UNPKG

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