|
1 |
| -import { Parser } from 'htmlparser2'; |
| 1 | +import SAXParser from 'parse5-sax-parser'; |
2 | 2 | import { isUrlRequest } from 'loader-utils';
|
3 | 3 |
|
4 | 4 | import HtmlSourceError from '../HtmlSourceError';
|
|
9 | 9 | normalizeUrl,
|
10 | 10 | requestify,
|
11 | 11 | isUrlRequestable,
|
| 12 | + c0ControlCodesExclude, |
12 | 13 | } from '../utils';
|
13 | 14 |
|
14 | 15 | export default (options) =>
|
@@ -36,186 +37,145 @@ export default (options) =>
|
36 | 37 | return false;
|
37 | 38 | }
|
38 | 39 |
|
| 40 | + const adaptedAttributes = attributes.reduce((accumulator, item) => { |
| 41 | + // eslint-disable-next-line no-param-reassign |
| 42 | + accumulator[item.name] = item.value; |
| 43 | + return accumulator; |
| 44 | + }, {}); |
| 45 | + |
39 | 46 | return element.filter
|
40 |
| - ? element.filter(tag, attribute, attributes, resourcePath) |
| 47 | + ? element.filter(tag, attribute, adaptedAttributes, resourcePath) |
41 | 48 | : true;
|
42 | 49 | });
|
43 | 50 | };
|
| 51 | + |
44 | 52 | const { resourcePath } = options;
|
45 |
| - const parser = new Parser( |
46 |
| - { |
47 |
| - attributesMeta: {}, |
48 |
| - onattribute(name, value) { |
49 |
| - // eslint-disable-next-line no-underscore-dangle |
50 |
| - const endIndex = parser._tokenizer._index; |
51 |
| - const startIndex = endIndex - value.length; |
52 |
| - const unquoted = html[endIndex] !== '"' && html[endIndex] !== "'"; |
53 |
| - |
54 |
| - this.attributesMeta[name] = { startIndex, unquoted }; |
55 |
| - }, |
56 |
| - onopentag(tag, attributes) { |
57 |
| - Object.keys(attributes).forEach((attribute) => { |
58 |
| - const value = attributes[attribute]; |
59 |
| - const { |
60 |
| - startIndex: valueStartIndex, |
61 |
| - unquoted, |
62 |
| - } = this.attributesMeta[attribute]; |
| 53 | + const parser5 = new SAXParser({ sourceCodeLocationInfo: true }); |
| 54 | + |
| 55 | + parser5.on('startTag', (node) => { |
| 56 | + const { tagName, attrs, sourceCodeLocation } = node; |
| 57 | + |
| 58 | + attrs.forEach((attribute) => { |
| 59 | + const { value, prefix } = attribute; |
| 60 | + let { name } = attribute; |
| 61 | + |
| 62 | + name = prefix ? `${prefix}:${name}` : name; |
| 63 | + |
| 64 | + if (!sourceCodeLocation.attrs[name]) { |
| 65 | + return; |
| 66 | + } |
| 67 | + |
| 68 | + const foundAttribute = getAttribute(tagName, name, attrs, resourcePath); |
63 | 69 |
|
64 |
| - const foundAttribute = getAttribute( |
65 |
| - tag, |
66 |
| - attribute, |
67 |
| - attributes, |
68 |
| - resourcePath |
69 |
| - ); |
| 70 | + if (!foundAttribute) { |
| 71 | + return; |
| 72 | + } |
| 73 | + |
| 74 | + const { type } = foundAttribute; |
| 75 | + |
| 76 | + const target = html.slice( |
| 77 | + sourceCodeLocation.attrs[name].startOffset, |
| 78 | + sourceCodeLocation.attrs[name].endOffset |
| 79 | + ); |
| 80 | + |
| 81 | + const unquoted = |
| 82 | + target[target.length - 1] !== '"' && |
| 83 | + target[target.length - 1] !== "'"; |
| 84 | + |
| 85 | + // eslint-disable-next-line default-case |
| 86 | + switch (type) { |
| 87 | + case 'src': { |
| 88 | + let source; |
| 89 | + |
| 90 | + try { |
| 91 | + source = parseSrc(value); |
| 92 | + } catch (error) { |
| 93 | + options.errors.push( |
| 94 | + new HtmlSourceError( |
| 95 | + `Bad value for attribute "${attribute.name}" on element "${tagName}": ${error.message}`, |
| 96 | + sourceCodeLocation.attrs[name].startOffset, |
| 97 | + sourceCodeLocation.attrs[name].endOffset, |
| 98 | + html |
| 99 | + ) |
| 100 | + ); |
70 | 101 |
|
71 |
| - if (!foundAttribute) { |
72 | 102 | return;
|
73 | 103 | }
|
74 | 104 |
|
75 |
| - const { type } = foundAttribute; |
76 |
| - |
77 |
| - // eslint-disable-next-line default-case |
78 |
| - switch (type) { |
79 |
| - case 'src': { |
80 |
| - let source; |
81 |
| - |
82 |
| - try { |
83 |
| - source = parseSrc(value); |
84 |
| - } catch (error) { |
85 |
| - options.errors.push( |
86 |
| - new HtmlSourceError( |
87 |
| - `Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, |
88 |
| - parser.startIndex, |
89 |
| - parser.endIndex, |
90 |
| - html |
91 |
| - ) |
92 |
| - ); |
93 |
| - |
94 |
| - return; |
95 |
| - } |
96 |
| - |
97 |
| - if (!isUrlRequestable(source.value, root)) { |
98 |
| - return; |
99 |
| - } |
100 |
| - |
101 |
| - const startIndex = valueStartIndex + source.startIndex; |
102 |
| - const endIndex = startIndex + source.value.length; |
103 |
| - |
104 |
| - sources.push({ |
105 |
| - name: attribute, |
106 |
| - value: source.value, |
107 |
| - unquoted, |
108 |
| - startIndex, |
109 |
| - endIndex, |
110 |
| - }); |
111 |
| - |
112 |
| - break; |
113 |
| - } |
114 |
| - case 'srcset': { |
115 |
| - let sourceSet; |
116 |
| - |
117 |
| - try { |
118 |
| - sourceSet = parseSrcset(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 |
| - sourceSet.forEach((sourceItem) => { |
133 |
| - const { source } = sourceItem; |
134 |
| - const startIndex = valueStartIndex + source.startIndex; |
135 |
| - const endIndex = startIndex + source.value.length; |
136 |
| - |
137 |
| - if (!isUrlRequestable(source.value, root)) { |
138 |
| - return; |
139 |
| - } |
140 |
| - |
141 |
| - sources.push({ |
142 |
| - name: attribute, |
143 |
| - value: source.value, |
144 |
| - unquoted, |
145 |
| - startIndex, |
146 |
| - endIndex, |
147 |
| - }); |
148 |
| - }); |
149 |
| - |
150 |
| - break; |
151 |
| - } |
152 |
| - // Need improve |
153 |
| - // case 'include': { |
154 |
| - // let source; |
155 |
| - // |
156 |
| - // // eslint-disable-next-line no-underscore-dangle |
157 |
| - // if (parser._tokenizer._state === 4) { |
158 |
| - // return; |
159 |
| - // } |
160 |
| - // |
161 |
| - // try { |
162 |
| - // source = parseSrc(value); |
163 |
| - // } catch (error) { |
164 |
| - // options.errors.push( |
165 |
| - // new HtmlSourceError( |
166 |
| - // `Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, |
167 |
| - // parser.startIndex, |
168 |
| - // parser.endIndex, |
169 |
| - // html |
170 |
| - // ) |
171 |
| - // ); |
172 |
| - // |
173 |
| - // return; |
174 |
| - // } |
175 |
| - // |
176 |
| - // if (!urlFilter(attribute, source.value, resourcePath)) { |
177 |
| - // return; |
178 |
| - // } |
179 |
| - // |
180 |
| - // const { startIndex } = parser; |
181 |
| - // const closingTag = html |
182 |
| - // .slice(startIndex - 1) |
183 |
| - // .match( |
184 |
| - // new RegExp(`<s*${tag}[^>]*>(?:.*?)</${tag}[^<>]*>`, 's') |
185 |
| - // ); |
186 |
| - // |
187 |
| - // if (!closingTag) { |
188 |
| - // return; |
189 |
| - // } |
190 |
| - // |
191 |
| - // const endIndex = startIndex + closingTag[0].length; |
192 |
| - // const importItem = getImportItem(source.value); |
193 |
| - // const replacementItem = getReplacementItem(importItem); |
194 |
| - // |
195 |
| - // sources.push({ replacementItem, startIndex, endIndex }); |
196 |
| - // |
197 |
| - // break; |
198 |
| - // } |
| 105 | + source = c0ControlCodesExclude(source); |
| 106 | + |
| 107 | + if (!isUrlRequestable(source.value, root)) { |
| 108 | + return; |
199 | 109 | }
|
200 |
| - }); |
201 |
| - |
202 |
| - this.attributesMeta = {}; |
203 |
| - }, |
204 |
| - onerror(error) { |
205 |
| - options.errors.push(error); |
206 |
| - }, |
207 |
| - }, |
208 |
| - { |
209 |
| - decodeEntities: false, |
210 |
| - lowerCaseTags: false, |
211 |
| - lowerCaseAttributeNames: false, |
212 |
| - recognizeCDATA: true, |
213 |
| - recognizeSelfClosing: true, |
214 |
| - } |
215 |
| - ); |
216 | 110 |
|
217 |
| - parser.write(html); |
218 |
| - parser.end(); |
| 111 | + const startOffset = |
| 112 | + sourceCodeLocation.attrs[name].startOffset + |
| 113 | + target.indexOf(source.value, name.length); |
| 114 | + |
| 115 | + sources.push({ |
| 116 | + name, |
| 117 | + value: source.value, |
| 118 | + unquoted, |
| 119 | + startIndex: startOffset, |
| 120 | + endIndex: startOffset + source.value.length, |
| 121 | + }); |
| 122 | + |
| 123 | + break; |
| 124 | + } |
| 125 | + |
| 126 | + case 'srcset': { |
| 127 | + let sourceSet; |
| 128 | + |
| 129 | + try { |
| 130 | + sourceSet = parseSrcset(value); |
| 131 | + } catch (error) { |
| 132 | + options.errors.push( |
| 133 | + new HtmlSourceError( |
| 134 | + `Bad value for attribute "${attribute.name}" on element "${tagName}": ${error.message}`, |
| 135 | + sourceCodeLocation.attrs[name].startOffset, |
| 136 | + sourceCodeLocation.attrs[name].endOffset, |
| 137 | + html |
| 138 | + ) |
| 139 | + ); |
| 140 | + |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + sourceSet = sourceSet.map((item) => ({ |
| 145 | + source: c0ControlCodesExclude(item.source), |
| 146 | + })); |
| 147 | + |
| 148 | + let searchFrom = name.length; |
| 149 | + |
| 150 | + sourceSet.forEach((sourceItem) => { |
| 151 | + const { source } = sourceItem; |
| 152 | + |
| 153 | + if (!isUrlRequestable(source.value, root)) { |
| 154 | + return; |
| 155 | + } |
| 156 | + |
| 157 | + const startOffset = |
| 158 | + sourceCodeLocation.attrs[name].startOffset + |
| 159 | + target.indexOf(source.value, searchFrom); |
| 160 | + |
| 161 | + searchFrom = target.indexOf(source.value, searchFrom) + 1; |
| 162 | + |
| 163 | + sources.push({ |
| 164 | + name, |
| 165 | + value: source.value, |
| 166 | + unquoted, |
| 167 | + startIndex: startOffset, |
| 168 | + endIndex: startOffset + source.value.length, |
| 169 | + }); |
| 170 | + }); |
| 171 | + |
| 172 | + break; |
| 173 | + } |
| 174 | + } |
| 175 | + }); |
| 176 | + }); |
| 177 | + |
| 178 | + parser5.end(html); |
219 | 179 |
|
220 | 180 | const imports = new Map();
|
221 | 181 | const replacements = new Map();
|
@@ -253,6 +213,7 @@ export default (options) =>
|
253 | 213 | const request = requestify(normalizedUrl, root);
|
254 | 214 | const newUrl = prefix ? `${prefix}!${request}` : request;
|
255 | 215 | const importKey = newUrl;
|
| 216 | + |
256 | 217 | let importName = imports.get(importKey);
|
257 | 218 |
|
258 | 219 | if (!importName) {
|
|
0 commit comments