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\\\\n\\\\n\\\\n\\\\n