single-file-node.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /* global require, exports, Buffer */
  2. const fs = require("fs");
  3. const jsdom = require("jsdom");
  4. const dataUri = require("strong-data-uri");
  5. const iconv = require("iconv-lite");
  6. const request = require("request-promise-native");
  7. const { JSDOM } = jsdom;
  8. const SCRIPTS = [
  9. "./lib/single-file/util/doc-util-core.js",
  10. "./lib/single-file/single-file-core.js",
  11. "./lib/single-file/vendor/css-tree.js",
  12. "./lib/single-file/util/doc-helper.js",
  13. "./lib/single-file/vendor/html-srcset-parser.js",
  14. "./lib/single-file/vendor/css-minifier.js",
  15. "./lib/single-file/modules/html-minifier.js",
  16. "./lib/single-file/modules/css-fonts-minifier.js",
  17. "./lib/single-file/modules/css-fonts-alt-minifier.js",
  18. "./lib/single-file/modules/css-matched-rules.js",
  19. "./lib/single-file/modules/css-medias-alt-minifier.js",
  20. "./lib/single-file/modules/css-rules-minifier.js",
  21. "./lib/single-file/modules/html-images-alt-minifier.js",
  22. "./lib/single-file/modules/html-serializer.js",
  23. "./lib/single-file/vendor/css-font-property-parser.js",
  24. "./lib/single-file/vendor/css-media-query-parser.js"
  25. ];
  26. SCRIPTS.forEach(scriptPath => eval(fs.readFileSync(scriptPath).toString()));
  27. const modules = {
  28. docHelper: this.docHelper,
  29. srcsetParser: this.srcsetParser,
  30. cssMinifier: this.cssMinifier,
  31. htmlMinifier: this.htmlMinifier,
  32. fontsMinifier: this.fontsMinifier,
  33. fontsAltMinifier: this.fontsAltMinifier,
  34. cssRulesMinifier: this.cssRulesMinifier,
  35. matchedRules: this.matchedRules,
  36. mediasMinifier: this.mediasMinifier,
  37. imagesAltMinifier: this.imagesAltMinifier,
  38. serializer: this.serializer
  39. };
  40. modules.fontsAltMinifier.cssTree = this.cssTree;
  41. modules.fontsMinifier.cssTree = this.cssTree;
  42. modules.fontsMinifier.fontPropertyParser = this.fontPropertyParser;
  43. modules.fontsMinifier.docHelper = this.docHelper;
  44. modules.matchedRules.cssTree = this.cssTree;
  45. modules.mediasMinifier.cssTree = this.cssTree;
  46. modules.mediasMinifier.mediaQueryParser = this.mediaQueryParser;
  47. modules.cssRulesMinifier.cssTree = this.cssTree;
  48. modules.imagesAltMinifier.srcsetParser = this.srcsetParser;
  49. const domUtil = {
  50. getContent, parseDocContent, parseSVGContent, isValidFontUrl, getContentSize, digestText
  51. };
  52. exports.getClass = () => {
  53. const DocUtil = this.DocUtilCore.getClass(modules, domUtil);
  54. return this.SingleFileCore.getClass(DocUtil, this.cssTree);
  55. };
  56. function parseDocContent(content, baseURI) {
  57. const doc = (new JSDOM(content, {
  58. contentType: "text/html"
  59. })).window.document;
  60. let baseElement = doc.querySelector("base");
  61. if (!baseElement || !baseElement.getAttribute("href")) {
  62. if (baseElement) {
  63. baseElement.remove();
  64. }
  65. baseElement = doc.createElement("base");
  66. baseElement.setAttribute("href", baseURI);
  67. doc.head.insertBefore(baseElement, doc.head.firstChild);
  68. }
  69. return doc;
  70. }
  71. function parseSVGContent(content) {
  72. return (new JSDOM(content, {
  73. contentType: "image/svg+xml"
  74. })).window.document;
  75. }
  76. async function digestText(/* algo, text */) {
  77. // TODO
  78. return 0;
  79. }
  80. function getContentSize(content) {
  81. // TODO: check
  82. return Buffer.byteLength(content, "utf-8");
  83. }
  84. function isValidFontUrl(/* urlFunction */) {
  85. // TODO?
  86. return true;
  87. }
  88. async function getContent(resourceURL, options) {
  89. const requestOptions = {
  90. method: "GET",
  91. uri: resourceURL,
  92. resolveWithFullResponse: true,
  93. encoding: null,
  94. headers: {
  95. "User-Agent": options.userAgent
  96. }
  97. };
  98. let resourceContent;
  99. try {
  100. resourceContent = await request(requestOptions);
  101. } catch (e) {
  102. return options.asDataURI ? "data:base64," : "";
  103. }
  104. const contentType = resourceContent.headers["content-type"];
  105. if (options && options.asDataURI) {
  106. try {
  107. return { data: dataUri.encode(resourceContent.body, contentType), resourceURL };
  108. } catch (e) {
  109. return { data: "data:base64,", resourceURL };
  110. }
  111. } else {
  112. const matchCharset = contentType && contentType.match(/\s*;\s*charset\s*=\s*(.*)(;|$)/i);
  113. let charset = "utf-8";
  114. if (matchCharset && matchCharset[1]) {
  115. charset = matchCharset[1];
  116. try {
  117. return { data: iconv.decode(resourceContent.body, charset), charset };
  118. } catch (e) {
  119. return { data: resourceContent.body.toString("utf8"), charset: "utf8" };
  120. }
  121. } else {
  122. return { data: resourceContent.body.toString("utf8"), charset };
  123. }
  124. }
  125. }