source-plugin.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _htmlparser = require("htmlparser2");
  7. var _loaderUtils = require("loader-utils");
  8. var _HtmlSourceError = _interopRequireDefault(require("../HtmlSourceError"));
  9. var _utils = require("../utils");
  10. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  11. var _default = options => function process(html) {
  12. const {
  13. list,
  14. urlFilter: maybeUrlFilter,
  15. root
  16. } = options.attributes;
  17. const sources = [];
  18. const urlFilter = (0, _utils.getFilter)(maybeUrlFilter, value => (0, _loaderUtils.isUrlRequest)(value, root));
  19. const getAttribute = (tag, attribute, attributes, resourcePath) => {
  20. return list.find(element => {
  21. const foundTag = typeof element.tag === 'undefined' || typeof element.tag !== 'undefined' && element.tag.toLowerCase() === tag.toLowerCase();
  22. const foundAttribute = element.attribute.toLowerCase() === attribute.toLowerCase();
  23. const isNotFiltered = element.filter ? element.filter(tag, attribute, attributes, resourcePath) : true;
  24. return foundTag && foundAttribute && isNotFiltered;
  25. });
  26. };
  27. const {
  28. resourcePath
  29. } = options;
  30. const parser = new _htmlparser.Parser({
  31. attributesMeta: {},
  32. onattribute(name, value) {
  33. // eslint-disable-next-line no-underscore-dangle
  34. const endIndex = parser._tokenizer._index;
  35. const startIndex = endIndex - value.length;
  36. const unquoted = html[endIndex] !== '"' && html[endIndex] !== "'";
  37. this.attributesMeta[name] = {
  38. startIndex,
  39. unquoted
  40. };
  41. },
  42. onopentag(tag, attributes) {
  43. Object.keys(attributes).forEach(attribute => {
  44. const value = attributes[attribute];
  45. const {
  46. startIndex: valueStartIndex,
  47. unquoted
  48. } = this.attributesMeta[attribute];
  49. const foundAttribute = getAttribute(tag, attribute, attributes, resourcePath);
  50. if (!foundAttribute) {
  51. return;
  52. }
  53. const {
  54. type
  55. } = foundAttribute; // eslint-disable-next-line default-case
  56. switch (type) {
  57. case 'src':
  58. {
  59. let source;
  60. try {
  61. source = (0, _utils.parseSrc)(value);
  62. } catch (error) {
  63. options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html));
  64. return;
  65. }
  66. if (!(0, _utils.isUrlRequestable)(source.value, root)) {
  67. return;
  68. }
  69. const startIndex = valueStartIndex + source.startIndex;
  70. const endIndex = startIndex + source.value.length;
  71. sources.push({
  72. name: attribute,
  73. value: source.value,
  74. unquoted,
  75. startIndex,
  76. endIndex
  77. });
  78. break;
  79. }
  80. case 'srcset':
  81. {
  82. let sourceSet;
  83. try {
  84. sourceSet = (0, _utils.parseSrcset)(value);
  85. } catch (error) {
  86. options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html));
  87. return;
  88. }
  89. sourceSet.forEach(sourceItem => {
  90. const {
  91. source
  92. } = sourceItem;
  93. const startIndex = valueStartIndex + source.startIndex;
  94. const endIndex = startIndex + source.value.length;
  95. if (!(0, _utils.isUrlRequestable)(source.value, root)) {
  96. return;
  97. }
  98. sources.push({
  99. name: attribute,
  100. value: source.value,
  101. unquoted,
  102. startIndex,
  103. endIndex
  104. });
  105. });
  106. break;
  107. }
  108. // Need improve
  109. // case 'include': {
  110. // let source;
  111. //
  112. // // eslint-disable-next-line no-underscore-dangle
  113. // if (parser._tokenizer._state === 4) {
  114. // return;
  115. // }
  116. //
  117. // try {
  118. // source = parseSrc(value);
  119. // } catch (error) {
  120. // options.errors.push(
  121. // new HtmlSourceError(
  122. // `Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`,
  123. // parser.startIndex,
  124. // parser.endIndex,
  125. // html
  126. // )
  127. // );
  128. //
  129. // return;
  130. // }
  131. //
  132. // if (!urlFilter(attribute, source.value, resourcePath)) {
  133. // return;
  134. // }
  135. //
  136. // const { startIndex } = parser;
  137. // const closingTag = html
  138. // .slice(startIndex - 1)
  139. // .match(
  140. // new RegExp(`<s*${tag}[^>]*>(?:.*?)</${tag}[^<>]*>`, 's')
  141. // );
  142. //
  143. // if (!closingTag) {
  144. // return;
  145. // }
  146. //
  147. // const endIndex = startIndex + closingTag[0].length;
  148. // const importItem = getImportItem(source.value);
  149. // const replacementItem = getReplacementItem(importItem);
  150. //
  151. // sources.push({ replacementItem, startIndex, endIndex });
  152. //
  153. // break;
  154. // }
  155. }
  156. });
  157. this.attributesMeta = {};
  158. },
  159. onerror(error) {
  160. options.errors.push(error);
  161. }
  162. }, {
  163. decodeEntities: false,
  164. lowerCaseTags: false,
  165. lowerCaseAttributeNames: false,
  166. recognizeCDATA: true,
  167. recognizeSelfClosing: true
  168. });
  169. parser.write(html);
  170. parser.end();
  171. const imports = new Map();
  172. const replacements = new Map();
  173. let offset = 0;
  174. for (const source of sources) {
  175. const {
  176. name,
  177. value,
  178. unquoted,
  179. startIndex,
  180. endIndex
  181. } = source;
  182. let normalizedUrl = value;
  183. let prefix = '';
  184. const queryParts = normalizedUrl.split('!');
  185. if (queryParts.length > 1) {
  186. normalizedUrl = queryParts.pop();
  187. prefix = queryParts.join('!');
  188. }
  189. normalizedUrl = (0, _utils.normalizeUrl)(normalizedUrl);
  190. if (!urlFilter(name, value, resourcePath)) {
  191. // eslint-disable-next-line no-continue
  192. continue;
  193. }
  194. let hash;
  195. const indexHash = normalizedUrl.lastIndexOf('#');
  196. if (indexHash >= 0) {
  197. hash = normalizedUrl.substr(indexHash, indexHash);
  198. normalizedUrl = normalizedUrl.substr(0, indexHash);
  199. }
  200. const request = (0, _utils.requestify)(normalizedUrl, root);
  201. const newUrl = prefix ? `${prefix}!${request}` : request;
  202. const importKey = newUrl;
  203. let importName = imports.get(importKey);
  204. if (!importName) {
  205. importName = `___HTML_LOADER_IMPORT_${imports.size}___`;
  206. imports.set(importKey, importName);
  207. options.imports.push({
  208. importName,
  209. source: options.urlHandler(newUrl)
  210. });
  211. }
  212. const replacementKey = JSON.stringify({
  213. newUrl,
  214. unquoted,
  215. hash
  216. });
  217. let replacementName = replacements.get(replacementKey);
  218. if (!replacementName) {
  219. replacementName = `___HTML_LOADER_REPLACEMENT_${replacements.size}___`;
  220. replacements.set(replacementKey, replacementName);
  221. options.replacements.push({
  222. replacementName,
  223. importName,
  224. hash,
  225. unquoted
  226. });
  227. } // eslint-disable-next-line no-param-reassign
  228. html = html.slice(0, startIndex + offset) + replacementName + html.slice(endIndex + offset);
  229. offset += startIndex + replacementName.length - endIndex;
  230. }
  231. return html;
  232. };
  233. exports.default = _default;