diff --git a/package-lock.json b/package-lock.json
index 65927609..109e4987 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14046,6 +14046,17 @@
}
}
},
+ "url-loader": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.0.tgz",
+ "integrity": "sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^2.0.0",
+ "mime-types": "^2.1.26",
+ "schema-utils": "^2.6.5"
+ }
+ },
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
diff --git a/package.json b/package.json
index 5cab3b66..ccb5a023 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"posthtml-webp": "^1.5.0",
"prettier": "^2.0.5",
"standard-version": "^8.0.2",
+ "url-loader": "^4.1.0",
"webpack": "^4.44.1"
},
"keywords": [
diff --git a/src/index.js b/src/index.js
index 3e98f981..46b3860f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-import { getOptions } from 'loader-utils';
+import { getOptions, stringifyRequest } from 'loader-utils';
import validateOptions from 'schema-utils';
import { sourcePlugin, minimizerPlugin } from './plugins';
@@ -35,6 +35,7 @@ export default async function loader(content) {
if (options.attributes) {
plugins.push(
sourcePlugin({
+ urlHandler: (url) => stringifyRequest(this, url),
attributes: options.attributes,
resourcePath: this.resourcePath,
imports,
diff --git a/src/plugins/source-plugin.js b/src/plugins/source-plugin.js
index 5d89f558..287e9908 100644
--- a/src/plugins/source-plugin.js
+++ b/src/plugins/source-plugin.js
@@ -1,25 +1,14 @@
-import { parse } from 'url';
-
import { Parser } from 'htmlparser2';
-import { isUrlRequest, urlToRequest } from 'loader-utils';
+import { isUrlRequest } from 'loader-utils';
import HtmlSourceError from '../HtmlSourceError';
-import { getFilter, parseSrc, parseSrcset } from '../utils';
-
-function parseSource(source) {
- const URLObject = parse(source);
- const { hash } = URLObject;
-
- if (!hash) {
- return { sourceValue: source };
- }
-
- URLObject.hash = null;
-
- const sourceWithoutHash = URLObject.format();
-
- return { sourceValue: sourceWithoutHash, hash };
-}
+import {
+ getFilter,
+ parseSrc,
+ parseSrcset,
+ normalizeUrl,
+ requestify,
+} from '../utils';
export default (options) =>
function process(html) {
@@ -44,45 +33,6 @@ export default (options) =>
});
};
const { resourcePath } = options;
- const imports = new Map();
- const getImportItem = (value) => {
- const key = urlToRequest(decodeURIComponent(value), root);
-
- let name = imports.get(key);
-
- if (name) {
- return { key, name };
- }
-
- name = `___HTML_LOADER_IMPORT_${imports.size}___`;
- imports.set(key, name);
-
- options.imports.push({ importName: name, source: key });
-
- return { key, name };
- };
- const replacements = new Map();
- const getReplacementItem = (importItem, unquoted, hash) => {
- const key = JSON.stringify({ key: importItem.key, unquoted, hash });
-
- let name = replacements.get(key);
-
- if (name) {
- return { key, name };
- }
-
- name = `___HTML_LOADER_REPLACEMENT_${replacements.size}___`;
- replacements.set(key, name);
-
- options.replacements.push({
- replacementName: name,
- importName: importItem.name,
- hash,
- unquoted,
- });
-
- return { key, name };
- };
const parser = new Parser(
{
attributesMeta: {},
@@ -135,21 +85,16 @@ export default (options) =>
return;
}
- if (!urlFilter(attribute, source.value, resourcePath)) {
- return;
- }
-
- const { sourceValue, hash } = parseSource(source.value);
- const importItem = getImportItem(sourceValue);
- const replacementItem = getReplacementItem(
- importItem,
- unquoted,
- hash
- );
const startIndex = valueStartIndex + source.startIndex;
const endIndex = startIndex + source.value.length;
- sources.push({ replacementItem, startIndex, endIndex });
+ sources.push({
+ name: attribute,
+ value: source.value,
+ unquoted,
+ startIndex,
+ endIndex,
+ });
break;
}
@@ -173,22 +118,16 @@ export default (options) =>
sourceSet.forEach((sourceItem) => {
const { source } = sourceItem;
-
- if (!urlFilter(attribute, source.value, resourcePath)) {
- return;
- }
-
- const { sourceValue, hash } = parseSource(source.value);
- const importItem = getImportItem(sourceValue);
- const replacementItem = getReplacementItem(
- importItem,
- unquoted,
- hash
- );
const startIndex = valueStartIndex + source.startIndex;
const endIndex = startIndex + source.value.length;
- sources.push({ replacementItem, startIndex, endIndex });
+ sources.push({
+ name: attribute,
+ value: source.value,
+ unquoted,
+ startIndex,
+ endIndex,
+ });
});
break;
@@ -261,18 +200,76 @@ export default (options) =>
parser.write(html);
parser.end();
+ const imports = new Map();
+ const replacements = new Map();
+
let offset = 0;
for (const source of sources) {
- const { startIndex, endIndex, replacementItem } = source;
+ const { name, value, unquoted, startIndex, endIndex } = source;
+
+ let normalizedUrl = value;
+ let prefix = '';
+
+ const queryParts = normalizedUrl.split('!');
+
+ if (queryParts.length > 1) {
+ normalizedUrl = queryParts.pop();
+ prefix = queryParts.join('!');
+ }
+
+ normalizedUrl = normalizeUrl(normalizedUrl);
+
+ if (!urlFilter(name, value, resourcePath)) {
+ // eslint-disable-next-line no-continue
+ continue;
+ }
+
+ let hash;
+ const indexHash = normalizedUrl.lastIndexOf('#');
+
+ if (indexHash >= 0) {
+ hash = normalizedUrl.substr(indexHash, indexHash);
+ normalizedUrl = normalizedUrl.substr(0, indexHash);
+ }
+
+ const request = requestify(normalizedUrl, root);
+ const newUrl = prefix ? `${prefix}!${request}` : request;
+ const importKey = newUrl;
+ let importName = imports.get(importKey);
+
+ if (!importName) {
+ importName = `___HTML_LOADER_IMPORT_${imports.size}___`;
+ imports.set(importKey, importName);
+
+ options.imports.push({
+ importName,
+ source: options.urlHandler(newUrl),
+ });
+ }
+
+ const replacementKey = JSON.stringify({ newUrl, unquoted, hash });
+ let replacementName = replacements.get(replacementKey);
+
+ if (!replacementName) {
+ replacementName = `___HTML_LOADER_REPLACEMENT_${replacements.size}___`;
+ replacements.set(replacementKey, replacementName);
+
+ options.replacements.push({
+ replacementName,
+ importName,
+ hash,
+ unquoted,
+ });
+ }
// eslint-disable-next-line no-param-reassign
html =
html.slice(0, startIndex + offset) +
- replacementItem.name +
+ replacementName +
html.slice(endIndex + offset);
- offset += startIndex + replacementItem.name.length - endIndex;
+ offset += startIndex + replacementName.length - endIndex;
}
return html;
diff --git a/src/utils.js b/src/utils.js
index 76cba1b1..15ed662b 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,4 +1,4 @@
-import { stringifyRequest } from 'loader-utils';
+import { stringifyRequest, urlToRequest } from 'loader-utils';
function isASCIIWhitespace(character) {
return (
@@ -370,6 +370,14 @@ export function parseSrc(input) {
return { value, startIndex };
}
+export function normalizeUrl(url) {
+ return decodeURIComponent(url);
+}
+
+export function requestify(url, root) {
+ return urlToRequest(url, root);
+}
+
function isProductionMode(loaderContext) {
return loaderContext.mode === 'production' || !loaderContext.mode;
}
@@ -606,11 +614,10 @@ export function getImportCode(html, loaderContext, imports, options) {
for (const item of imports) {
const { importName, source } = item;
- const stringifiedSourceRequest = stringifyRequest(loaderContext, source);
code += options.esModule
- ? `import ${importName} from ${stringifiedSourceRequest};\n`
- : `var ${importName} = require(${stringifiedSourceRequest});\n`;
+ ? `import ${importName} from ${source};\n`
+ : `var ${importName} = require(${source});\n`;
}
return `// Imports\n${code}`;
diff --git a/test/__snapshots__/attributes-option.test.js.snap b/test/__snapshots__/attributes-option.test.js.snap
index 418a0a4a..13d29dd2 100644
--- a/test/__snapshots__/attributes-option.test.js.snap
+++ b/test/__snapshots__/attributes-option.test.js.snap
@@ -764,7 +764,7 @@ var ___HTML_LOADER_REPLACEMENT_6___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(_
var ___HTML_LOADER_REPLACEMENT_7___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_7___);
var ___HTML_LOADER_REPLACEMENT_8___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_8___);
var ___HTML_LOADER_REPLACEMENT_9___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_9___);
-var code = \\"\\\\n\\\\n
My First Heading
\\\\nMy first paragraph.
\\\\nAn Unordered HTML List
\\\\n\\\\n\\\\n - Coffee
\\\\n - Tea
\\\\n - Milk
\\\\n
\\\\n\\\\nAn Ordered HTML List
\\\\n\\\\n\\\\n - Coffee
\\\\n - Tea
\\\\n - Milk
\\\\n
\\\\n\\\\n\\\\n\\\\n\\\\n\\\\nFoo
\\\\n\\\\n\\\\nBAR
\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n