Socket
Socket
Sign inDemoInstall

html-loader

Package Overview
Dependencies
Maintainers
10
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

html-loader - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

16

CHANGELOG.md

@@ -5,2 +5,18 @@ # Changelog

## [1.2.0](https://github.com/webpack-contrib/html-loader/compare/v1.1.0...v1.2.0) (2020-08-18)
### Features
* support SVG tags ([#302](https://github.com/webpack-contrib/html-loader/issues/302)) ([1acd204](https://github.com/webpack-contrib/html-loader/commit/1acd20448dbe976b883597b135bb8ac9e1b71d1a))
### Bug Fixes
* do not handle non standard `script` types ([ddad9f2](https://github.com/webpack-contrib/html-loader/commit/ddad9f2d6f5ab75fe2afd247bf55b1646c6e1c31))
* inline syntax for sources ([#310](https://github.com/webpack-contrib/html-loader/issues/310)) ([c247cfa](https://github.com/webpack-contrib/html-loader/commit/c247cfa9ad66281b28aef5397c8c2d2786f05867))
* linefeed characters in sources ([#311](https://github.com/webpack-contrib/html-loader/issues/311)) ([b8ee9ee](https://github.com/webpack-contrib/html-loader/commit/b8ee9ee0d60848e84e52fb117c1f3cdc2ebf08d7))
* minimize is more safely ([#304](https://github.com/webpack-contrib/html-loader/issues/304)) ([03152b1](https://github.com/webpack-contrib/html-loader/commit/03152b1d3b807a287d84302f6a9987ceb22d395c))
* perf ([#300](https://github.com/webpack-contrib/html-loader/issues/300)) ([d69f259](https://github.com/webpack-contrib/html-loader/commit/d69f259d2a6b4bc9ba9c163fd2d70989c3f3a6ff))
## [1.1.0](https://github.com/webpack-contrib/html-loader/compare/v1.0.0...v1.1.0) (2020-04-02)

@@ -7,0 +23,0 @@

72

dist/index.js

@@ -6,3 +6,3 @@ "use strict";

});
exports.default = htmlLoader;
exports.default = loader;

@@ -21,8 +21,9 @@ var _loaderUtils = require("loader-utils");

async function htmlLoader(content) {
const options = (0, _loaderUtils.getOptions)(this);
(0, _schemaUtils.default)(_options.default, options, {
async function loader(content) {
const rawOptions = (0, _loaderUtils.getOptions)(this);
(0, _schemaUtils.default)(_options.default, rawOptions, {
name: 'HTML Loader',
baseDataPath: 'options'
});
const options = (0, _utils.normalizeOptions)(rawOptions, this);

@@ -35,16 +36,21 @@ if (options.preprocessor) {

const plugins = [];
const attributes = typeof options.attributes === 'undefined' ? true : options.attributes;
const errors = [];
const imports = [];
const replacements = [];
if (attributes) {
if (options.attributes) {
plugins.push((0, _plugins.sourcePlugin)({
attributes,
resourcePath: this.resourcePath
urlHandler: url => (0, _loaderUtils.stringifyRequest)(this, url),
attributes: options.attributes,
resourcePath: this.resourcePath,
imports,
errors,
replacements
}));
}
const minimize = typeof options.minimize === 'undefined' ? (0, _utils.isProductionMode)(this) : options.minimize;
if (minimize) {
if (options.minimize) {
plugins.push((0, _plugins.minimizerPlugin)({
minimize
minimize: options.minimize,
errors
}));

@@ -54,27 +60,5 @@ }

const {
html,
messages
html
} = (0, _utils.pluginRunner)(plugins).process(content);
const errors = [];
const importedMessages = [];
const replaceableMessages = [];
const exportedMessages = [];
for (const message of messages) {
// eslint-disable-next-line default-case
switch (message.type) {
case 'error':
errors.push(message.value);
break;
case 'import':
importedMessages.push(message.value);
break;
case 'replacer':
replaceableMessages.push(message.value);
break;
}
}
for (const error of errors) {

@@ -84,16 +68,6 @@ this.emitError(error instanceof Error ? error : new Error(error));

const codeOptions = { ...options,
loaderContext: this
};
const importCode = (0, _utils.getImportCode)(html, importedMessages, codeOptions);
const moduleCode = (0, _utils.getModuleCode)(html, replaceableMessages, codeOptions);
const exportCode = (0, _utils.getExportCode)(html, exportedMessages, codeOptions);
let code = `${importCode}${moduleCode}${exportCode}`;
if (options.process && options.process.post) {
// eslint-disable-next-line no-param-reassign
code = options.process.post(code, this);
}
return code;
const importCode = (0, _utils.getImportCode)(html, this, imports, options);
const moduleCode = (0, _utils.getModuleCode)(html, replacements, options);
const exportCode = (0, _utils.getExportCode)(html, options);
return `${importCode}${moduleCode}${exportCode}`;
}

@@ -10,24 +10,8 @@ "use strict";

var _default = options => function process(html, result) {
const minimizeOptions = typeof options.minimize === 'boolean' || typeof options.minimize === 'undefined' ? {
collapseWhitespace: true,
conservativeCollapse: true,
keepClosingSlash: true,
minifyCSS: true,
minifyJS: true,
removeAttributeQuotes: true,
removeComments: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
} : options.minimize;
var _default = options => function process(html) {
try {
// eslint-disable-next-line no-param-reassign
html = (0, _htmlMinifierTerser.minify)(html, minimizeOptions);
html = (0, _htmlMinifierTerser.minify)(html, options.minimize);
} catch (error) {
result.messages.push({
type: 'error',
value: error
});
options.errors.push(error);
}

@@ -34,0 +18,0 @@

@@ -8,4 +8,2 @@ "use strict";

var _url = require("url");
var _htmlparser = require("htmlparser2");

@@ -21,430 +19,8 @@

function isASCIIWhitespace(character) {
return (// Horizontal tab
character === '\u0009' || // New line
character === '\u000A' || // Form feed
character === '\u000C' || // Carriage return
character === '\u000D' || // Space
character === '\u0020'
);
} // (Don't use \s, to avoid matching non-breaking space)
// eslint-disable-next-line no-control-regex
const regexLeadingSpaces = /^[ \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/;
const regexTrailingCommas = /[,]+$/;
const regexNonNegativeInteger = /^\d+$/; // ( Positive or negative or unsigned integers or decimals, without or without exponents.
// Must include at least one digit.
// According to spec tests any decimal point must be followed by a digit.
// No leading plus sign is allowed.)
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
function parseSrcset(input) {
// 1. Let input be the value passed to this algorithm.
const inputLength = input.length;
let url;
let descriptors;
let currentDescriptor;
let state;
let c; // 2. Let position be a pointer into input, initially pointing at the start
// of the string.
let position = 0;
let startUrlPosition; // eslint-disable-next-line consistent-return
function collectCharacters(regEx) {
let chars;
const match = regEx.exec(input.substring(position));
if (match) {
[chars] = match;
position += chars.length;
return chars;
}
} // 3. Let candidates be an initially empty source set.
const candidates = []; // 4. Splitting loop: Collect a sequence of characters that are space
// characters or U+002C COMMA characters. If any U+002C COMMA characters
// were collected, that is a parse error.
// eslint-disable-next-line no-constant-condition
while (true) {
collectCharacters(regexLeadingCommasOrSpaces); // 5. If position is past the end of input, return candidates and abort these steps.
if (position >= inputLength) {
if (candidates.length === 0) {
throw new Error('Must contain one or more image candidate strings');
} // (we're done, this is the sole return path)
return candidates;
} // 6. Collect a sequence of characters that are not space characters,
// and let that be url.
startUrlPosition = position;
url = collectCharacters(regexLeadingNotSpaces); // 7. Let descriptors be a new empty list.
descriptors = []; // 8. If url ends with a U+002C COMMA character (,), follow these substeps:
// (1). Remove all trailing U+002C COMMA characters from url. If this removed
// more than one character, that is a parse error.
if (url.slice(-1) === ',') {
url = url.replace(regexTrailingCommas, ''); // (Jump ahead to step 9 to skip tokenization and just push the candidate).
parseDescriptors();
} // Otherwise, follow these substeps:
else {
tokenize();
} // 16. Return to the step labeled splitting loop.
}
/**
* Tokenizes descriptor properties prior to parsing
* Returns undefined.
*/
function tokenize() {
// 8.1. Descriptor tokenizer: Skip whitespace
collectCharacters(regexLeadingSpaces); // 8.2. Let current descriptor be the empty string.
currentDescriptor = ''; // 8.3. Let state be in descriptor.
state = 'in descriptor'; // eslint-disable-next-line no-constant-condition
while (true) {
// 8.4. Let c be the character at position.
c = input.charAt(position); // Do the following depending on the value of state.
// For the purpose of this step, "EOF" is a special character representing
// that position is past the end of input.
// In descriptor
if (state === 'in descriptor') {
// Do the following, depending on the value of c:
// Space character
// If current descriptor is not empty, append current descriptor to
// descriptors and let current descriptor be the empty string.
// Set state to after descriptor.
if (isASCIIWhitespace(c)) {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
currentDescriptor = '';
state = 'after descriptor';
}
} // U+002C COMMA (,)
// Advance position to the next character in input. If current descriptor
// is not empty, append current descriptor to descriptors. Jump to the step
// labeled descriptor parser.
else if (c === ',') {
position += 1;
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return;
} // U+0028 LEFT PARENTHESIS (()
// Append c to current descriptor. Set state to in parens.
else if (c === '\u0028') {
currentDescriptor += c;
state = 'in parens';
} // EOF
// If current descriptor is not empty, append current descriptor to
// descriptors. Jump to the step labeled descriptor parser.
else if (c === '') {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return; // Anything else
// Append c to current descriptor.
} else {
currentDescriptor += c;
}
} // In parens
else if (state === 'in parens') {
// U+0029 RIGHT PARENTHESIS ())
// Append c to current descriptor. Set state to in descriptor.
if (c === ')') {
currentDescriptor += c;
state = 'in descriptor';
} // EOF
// Append current descriptor to descriptors. Jump to the step labeled
// descriptor parser.
else if (c === '') {
descriptors.push(currentDescriptor);
parseDescriptors();
return;
} // Anything else
// Append c to current descriptor.
else {
currentDescriptor += c;
}
} // After descriptor
else if (state === 'after descriptor') {
// Do the following, depending on the value of c:
if (isASCIIWhitespace(c)) {} // Space character: Stay in this state.
// EOF: Jump to the step labeled descriptor parser.
else if (c === '') {
parseDescriptors();
return;
} // Anything else
// Set state to in descriptor. Set position to the previous character in input.
else {
state = 'in descriptor';
position -= 1;
}
} // Advance position to the next character in input.
position += 1;
}
}
/**
* Adds descriptor properties to a candidate, pushes to the candidates array
* @return undefined
*/
// Declared outside of the while loop so that it's only created once.
function parseDescriptors() {
// 9. Descriptor parser: Let error be no.
let pError = false; // 10. Let width be absent.
// 11. Let density be absent.
// 12. Let future-compat-h be absent. (We're implementing it now as h)
let w;
let d;
let h;
let i;
const candidate = {};
let desc;
let lastChar;
let value;
let intVal;
let floatVal; // 13. For each descriptor in descriptors, run the appropriate set of steps
// from the following list:
for (i = 0; i < descriptors.length; i++) {
desc = descriptors[i];
lastChar = desc[desc.length - 1];
value = desc.substring(0, desc.length - 1);
intVal = parseInt(value, 10);
floatVal = parseFloat(value); // If the descriptor consists of a valid non-negative integer followed by
// a U+0077 LATIN SMALL LETTER W character
if (regexNonNegativeInteger.test(value) && lastChar === 'w') {
// If width and density are not both absent, then let error be yes.
if (w || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes.
// Otherwise, let width be the result.
if (intVal === 0) {
pError = true;
} else {
w = intVal;
}
} // If the descriptor consists of a valid floating-point number followed by
// a U+0078 LATIN SMALL LETTER X character
else if (regexFloatingPoint.test(value) && lastChar === 'x') {
// If width, density and future-compat-h are not all absent, then let error
// be yes.
if (w || d || h) {
pError = true;
} // Apply the rules for parsing floating-point number values to the descriptor.
// If the result is less than zero, let error be yes. Otherwise, let density
// be the result.
if (floatVal < 0) {
pError = true;
} else {
d = floatVal;
}
} // If the descriptor consists of a valid non-negative integer followed by
// a U+0068 LATIN SMALL LETTER H character
else if (regexNonNegativeInteger.test(value) && lastChar === 'h') {
// If height and density are not both absent, then let error be yes.
if (h || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes. Otherwise, let future-compat-h
// be the result.
if (intVal === 0) {
pError = true;
} else {
h = intVal;
} // Anything else, Let error be yes.
} else {
pError = true;
}
} // 15. If error is still no, then append a new image source to candidates whose
// URL is url, associated with a width width if not absent and a pixel
// density density if not absent. Otherwise, there is a parse error.
if (!pError) {
candidate.source = {
value: url,
startIndex: startUrlPosition
};
if (w) {
candidate.width = {
value: w
};
}
if (d) {
candidate.density = {
value: d
};
}
if (h) {
candidate.height = {
value: h
};
}
candidates.push(candidate);
} else {
throw new Error(`Invalid srcset descriptor found in '${input}' at '${desc}'`);
}
}
}
function parseSrc(input) {
if (!input) {
throw new Error('Must be non-empty');
}
let startIndex = 0;
let value = input;
while (isASCIIWhitespace(value.substring(0, 1))) {
startIndex += 1;
value = value.substring(1, value.length);
}
while (isASCIIWhitespace(value.substring(value.length - 1, value.length))) {
value = value.substring(0, value.length - 1);
}
if (!value) {
throw new Error('Must be non-empty');
}
return {
value,
startIndex
};
}
function getAttributeValue(attributes, name) {
const lowercasedAttributes = Object.keys(attributes).reduce((keys, k) => {
// eslint-disable-next-line no-param-reassign
keys[k.toLowerCase()] = k;
return keys;
}, {});
return attributes[lowercasedAttributes[name.toLowerCase()]];
}
const defaultAttributes = [{
tag: 'audio',
attribute: 'src',
type: 'src'
}, {
tag: 'embed',
attribute: 'src',
type: 'src'
}, {
tag: 'img',
attribute: 'src',
type: 'src'
}, {
tag: 'img',
attribute: 'srcset',
type: 'srcset'
}, {
tag: 'input',
attribute: 'src',
type: 'src'
}, {
tag: 'link',
attribute: 'href',
type: 'src',
filter: (tag, attribute, attributes) => {
if (!/stylesheet/i.test(getAttributeValue(attributes, 'rel'))) {
return false;
}
if (attributes.type && getAttributeValue(attributes, 'type').trim().toLowerCase() !== 'text/css') {
return false;
}
return true;
}
}, {
tag: 'object',
attribute: 'data',
type: 'src'
}, {
tag: 'script',
attribute: 'src',
type: 'src'
}, {
tag: 'source',
attribute: 'src',
type: 'src'
}, {
tag: 'source',
attribute: 'srcset',
type: 'srcset'
}, {
tag: 'track',
attribute: 'src',
type: 'src'
}, {
tag: 'video',
attribute: 'poster',
type: 'src'
}, {
tag: 'video',
attribute: 'src',
type: 'src'
}];
var _default = options => function process(html, result) {
let attributeList;
let maybeUrlFilter;
let root;
if (typeof options.attributes === 'undefined' || options.attributes === true) {
attributeList = defaultAttributes;
} else {
attributeList = options.attributes.list || defaultAttributes; // eslint-disable-next-line no-undefined
({
urlFilter: maybeUrlFilter,
root
} = options.attributes);
}
var _default = options => function process(html) {
const {
list,
urlFilter: maybeUrlFilter,
root
} = options.attributes;
const sources = [];

@@ -454,3 +30,8 @@ const urlFilter = (0, _utils.getFilter)(maybeUrlFilter, value => (0, _loaderUtils.isUrlRequest)(value, root));

const getAttribute = (tag, attribute, attributes, resourcePath) => {
return attributeList.find(element => (typeof element.tag === 'undefined' || typeof element.tag !== 'undefined' && element.tag.toLowerCase() === tag.toLowerCase()) && element.attribute.toLowerCase() === attribute.toLowerCase() && (element.filter ? element.filter(tag, attribute, attributes, resourcePath) : true));
return list.find(element => {
const foundTag = typeof element.tag === 'undefined' || typeof element.tag !== 'undefined' && element.tag.toLowerCase() === tag.toLowerCase();
const foundAttribute = element.attribute.toLowerCase() === attribute.toLowerCase();
const isNotFiltered = element.filter ? element.filter(tag, attribute, attributes, resourcePath) : true;
return foundTag && foundAttribute && isNotFiltered;
});
};

@@ -490,58 +71,103 @@

type
} = foundAttribute;
} = foundAttribute; // eslint-disable-next-line default-case
if (type === 'srcset') {
let sourceSet;
switch (type) {
case 'src':
{
let source;
try {
sourceSet = parseSrcset(value);
} catch (error) {
result.messages.push({
type: 'error',
value: new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html)
});
return;
}
try {
source = (0, _utils.parseSrc)(value);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html));
return;
}
sourceSet.forEach(sourceItem => {
const {
source
} = sourceItem;
if (!urlFilter(attribute, source.value, resourcePath)) {
return;
const startIndex = valueStartIndex + source.startIndex;
const endIndex = startIndex + source.value.length;
sources.push({
name: attribute,
value: source.value,
unquoted,
startIndex,
endIndex
});
break;
}
const startIndex = valueStartIndex + source.startIndex;
sources.push({
startIndex,
value: source.value,
unquoted
});
});
return;
}
case 'srcset':
{
let sourceSet;
let source;
try {
sourceSet = (0, _utils.parseSrcset)(value);
} catch (error) {
options.errors.push(new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html));
return;
}
try {
source = parseSrc(value);
} catch (error) {
result.messages.push({
type: 'error',
value: new _HtmlSourceError.default(`Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`, parser.startIndex, parser.endIndex, html)
});
return;
sourceSet.forEach(sourceItem => {
const {
source
} = sourceItem;
const startIndex = valueStartIndex + source.startIndex;
const endIndex = startIndex + source.value.length;
sources.push({
name: attribute,
value: source.value,
unquoted,
startIndex,
endIndex
});
});
break;
}
// Need improve
// case 'include': {
// let source;
//
// // eslint-disable-next-line no-underscore-dangle
// if (parser._tokenizer._state === 4) {
// return;
// }
//
// try {
// source = parseSrc(value);
// } catch (error) {
// options.errors.push(
// new HtmlSourceError(
// `Bad value for attribute "${attribute}" on element "${tag}": ${error.message}`,
// parser.startIndex,
// parser.endIndex,
// html
// )
// );
//
// return;
// }
//
// if (!urlFilter(attribute, source.value, resourcePath)) {
// return;
// }
//
// const { startIndex } = parser;
// const closingTag = html
// .slice(startIndex - 1)
// .match(
// new RegExp(`<s*${tag}[^>]*>(?:.*?)</${tag}[^<>]*>`, 's')
// );
//
// if (!closingTag) {
// return;
// }
//
// const endIndex = startIndex + closingTag[0].length;
// const importItem = getImportItem(source.value);
// const replacementItem = getReplacementItem(importItem);
//
// sources.push({ replacementItem, startIndex, endIndex });
//
// break;
// }
}
if (!urlFilter(attribute, source.value, resourcePath)) {
return;
}
const startIndex = valueStartIndex + source.startIndex;
sources.push({
startIndex,
value: source.value,
unquoted
});
});

@@ -552,6 +178,3 @@ this.attributesMeta = {};

onerror(error) {
result.messages.push({
type: 'error',
value: error
});
options.errors.push(error);
}

@@ -568,4 +191,4 @@

parser.end();
const importsMap = new Map();
const replacersMap = new Map();
const imports = new Map();
const replacements = new Map();
let offset = 0;

@@ -575,61 +198,67 @@

const {
name,
value,
unquoted,
startIndex,
unquoted
endIndex
} = source;
let {
value
} = source;
const URLObject = (0, _url.parse)(value);
const {
hash
} = URLObject;
let normalizedUrl = value;
let prefix = '';
const queryParts = normalizedUrl.split('!');
if (hash) {
URLObject.hash = null;
source.value = URLObject.format();
value = value.slice(0, value.length - hash.length);
if (queryParts.length > 1) {
normalizedUrl = queryParts.pop();
prefix = queryParts.join('!');
}
const importKey = (0, _loaderUtils.urlToRequest)(decodeURIComponent(source.value), root);
let importName = importsMap.get(importKey);
normalizedUrl = (0, _utils.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 = (0, _utils.requestify)(normalizedUrl, root);
const newUrl = prefix ? `${prefix}!${request}` : request;
const importKey = newUrl;
let importName = imports.get(importKey);
if (!importName) {
importName = `___HTML_LOADER_IMPORT_${importsMap.size}___`;
importsMap.set(importKey, importName);
result.messages.push({
type: 'import',
value: {
type: 'source',
source: importKey,
importName
}
importName = `___HTML_LOADER_IMPORT_${imports.size}___`;
imports.set(importKey, importName);
options.imports.push({
importName,
source: options.urlHandler(newUrl)
});
}
const replacerKey = JSON.stringify({
importKey,
const replacementKey = JSON.stringify({
newUrl,
unquoted,
hash
});
let replacerName = replacersMap.get(replacerKey);
let replacementName = replacements.get(replacementKey);
if (!replacerName) {
replacerName = `___HTML_LOADER_REPLACER_${replacersMap.size}___`;
replacersMap.set(replacerKey, replacerName);
result.messages.push({
type: 'replacer',
value: {
type: 'source',
hash,
importName,
replacerName,
unquoted
}
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
const valueLength = hash ? value.length + hash.length : value.length; // eslint-disable-next-line no-param-reassign
html = html.substr(0, startIndex + offset) + replacerName + html.substr(startIndex + valueLength + offset);
offset += replacerName.length - valueLength;
html = html.slice(0, startIndex + offset) + replacementName + html.slice(endIndex + offset);
offset += startIndex + replacementName.length - endIndex;
}

@@ -636,0 +265,0 @@

@@ -6,5 +6,9 @@ "use strict";

});
exports.parseSrcset = parseSrcset;
exports.parseSrc = parseSrc;
exports.normalizeUrl = normalizeUrl;
exports.requestify = requestify;
exports.normalizeOptions = normalizeOptions;
exports.pluginRunner = pluginRunner;
exports.getFilter = getFilter;
exports.isProductionMode = isProductionMode;
exports.getImportCode = getImportCode;

@@ -16,10 +20,518 @@ exports.getModuleCode = getModuleCode;

const GET_SOURCE_FROM_IMPORT_NAME = '___HTML_LOADER_GET_SOURCE_FROM_IMPORT___';
function isASCIIWhitespace(character) {
return (// Horizontal tab
character === '\u0009' || // New line
character === '\u000A' || // Form feed
character === '\u000C' || // Carriage return
character === '\u000D' || // Space
character === '\u0020'
);
} // (Don't use \s, to avoid matching non-breaking space)
// eslint-disable-next-line no-control-regex
const regexLeadingSpaces = /^[ \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/; // eslint-disable-next-line no-control-regex
const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/;
const regexTrailingCommas = /[,]+$/;
const regexNonNegativeInteger = /^\d+$/; // ( Positive or negative or unsigned integers or decimals, without or without exponents.
// Must include at least one digit.
// According to spec tests any decimal point must be followed by a digit.
// No leading plus sign is allowed.)
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
function parseSrcset(input) {
// 1. Let input be the value passed to this algorithm.
const inputLength = input.length;
let url;
let descriptors;
let currentDescriptor;
let state;
let c; // 2. Let position be a pointer into input, initially pointing at the start
// of the string.
let position = 0;
let startUrlPosition; // eslint-disable-next-line consistent-return
function collectCharacters(regEx) {
let chars;
const match = regEx.exec(input.substring(position));
if (match) {
[chars] = match;
position += chars.length;
return chars;
}
} // 3. Let candidates be an initially empty source set.
const candidates = []; // 4. Splitting loop: Collect a sequence of characters that are space
// characters or U+002C COMMA characters. If any U+002C COMMA characters
// were collected, that is a parse error.
// eslint-disable-next-line no-constant-condition
while (true) {
collectCharacters(regexLeadingCommasOrSpaces); // 5. If position is past the end of input, return candidates and abort these steps.
if (position >= inputLength) {
if (candidates.length === 0) {
throw new Error('Must contain one or more image candidate strings');
} // (we're done, this is the sole return path)
return candidates;
} // 6. Collect a sequence of characters that are not space characters,
// and let that be url.
startUrlPosition = position;
url = collectCharacters(regexLeadingNotSpaces); // 7. Let descriptors be a new empty list.
descriptors = []; // 8. If url ends with a U+002C COMMA character (,), follow these substeps:
// (1). Remove all trailing U+002C COMMA characters from url. If this removed
// more than one character, that is a parse error.
if (url.slice(-1) === ',') {
url = url.replace(regexTrailingCommas, ''); // (Jump ahead to step 9 to skip tokenization and just push the candidate).
parseDescriptors();
} // Otherwise, follow these substeps:
else {
tokenize();
} // 16. Return to the step labeled splitting loop.
}
/**
* Tokenizes descriptor properties prior to parsing
* Returns undefined.
*/
function tokenize() {
// 8.1. Descriptor tokenizer: Skip whitespace
collectCharacters(regexLeadingSpaces); // 8.2. Let current descriptor be the empty string.
currentDescriptor = ''; // 8.3. Let state be in descriptor.
state = 'in descriptor'; // eslint-disable-next-line no-constant-condition
while (true) {
// 8.4. Let c be the character at position.
c = input.charAt(position); // Do the following depending on the value of state.
// For the purpose of this step, "EOF" is a special character representing
// that position is past the end of input.
// In descriptor
if (state === 'in descriptor') {
// Do the following, depending on the value of c:
// Space character
// If current descriptor is not empty, append current descriptor to
// descriptors and let current descriptor be the empty string.
// Set state to after descriptor.
if (isASCIIWhitespace(c)) {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
currentDescriptor = '';
state = 'after descriptor';
}
} // U+002C COMMA (,)
// Advance position to the next character in input. If current descriptor
// is not empty, append current descriptor to descriptors. Jump to the step
// labeled descriptor parser.
else if (c === ',') {
position += 1;
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return;
} // U+0028 LEFT PARENTHESIS (()
// Append c to current descriptor. Set state to in parens.
else if (c === '\u0028') {
currentDescriptor += c;
state = 'in parens';
} // EOF
// If current descriptor is not empty, append current descriptor to
// descriptors. Jump to the step labeled descriptor parser.
else if (c === '') {
if (currentDescriptor) {
descriptors.push(currentDescriptor);
}
parseDescriptors();
return; // Anything else
// Append c to current descriptor.
} else {
currentDescriptor += c;
}
} // In parens
else if (state === 'in parens') {
// U+0029 RIGHT PARENTHESIS ())
// Append c to current descriptor. Set state to in descriptor.
if (c === ')') {
currentDescriptor += c;
state = 'in descriptor';
} // EOF
// Append current descriptor to descriptors. Jump to the step labeled
// descriptor parser.
else if (c === '') {
descriptors.push(currentDescriptor);
parseDescriptors();
return;
} // Anything else
// Append c to current descriptor.
else {
currentDescriptor += c;
}
} // After descriptor
else if (state === 'after descriptor') {
// Do the following, depending on the value of c:
if (isASCIIWhitespace(c)) {// Space character: Stay in this state.
} // EOF: Jump to the step labeled descriptor parser.
else if (c === '') {
parseDescriptors();
return;
} // Anything else
// Set state to in descriptor. Set position to the previous character in input.
else {
state = 'in descriptor';
position -= 1;
}
} // Advance position to the next character in input.
position += 1;
}
}
/**
* Adds descriptor properties to a candidate, pushes to the candidates array
* @return undefined
*/
// Declared outside of the while loop so that it's only created once.
function parseDescriptors() {
// 9. Descriptor parser: Let error be no.
let pError = false; // 10. Let width be absent.
// 11. Let density be absent.
// 12. Let future-compat-h be absent. (We're implementing it now as h)
let w;
let d;
let h;
let i;
const candidate = {};
let desc;
let lastChar;
let value;
let intVal;
let floatVal; // 13. For each descriptor in descriptors, run the appropriate set of steps
// from the following list:
for (i = 0; i < descriptors.length; i++) {
desc = descriptors[i];
lastChar = desc[desc.length - 1];
value = desc.substring(0, desc.length - 1);
intVal = parseInt(value, 10);
floatVal = parseFloat(value); // If the descriptor consists of a valid non-negative integer followed by
// a U+0077 LATIN SMALL LETTER W character
if (regexNonNegativeInteger.test(value) && lastChar === 'w') {
// If width and density are not both absent, then let error be yes.
if (w || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes.
// Otherwise, let width be the result.
if (intVal === 0) {
pError = true;
} else {
w = intVal;
}
} // If the descriptor consists of a valid floating-point number followed by
// a U+0078 LATIN SMALL LETTER X character
else if (regexFloatingPoint.test(value) && lastChar === 'x') {
// If width, density and future-compat-h are not all absent, then let error
// be yes.
if (w || d || h) {
pError = true;
} // Apply the rules for parsing floating-point number values to the descriptor.
// If the result is less than zero, let error be yes. Otherwise, let density
// be the result.
if (floatVal < 0) {
pError = true;
} else {
d = floatVal;
}
} // If the descriptor consists of a valid non-negative integer followed by
// a U+0068 LATIN SMALL LETTER H character
else if (regexNonNegativeInteger.test(value) && lastChar === 'h') {
// If height and density are not both absent, then let error be yes.
if (h || d) {
pError = true;
} // Apply the rules for parsing non-negative integers to the descriptor.
// If the result is zero, let error be yes. Otherwise, let future-compat-h
// be the result.
if (intVal === 0) {
pError = true;
} else {
h = intVal;
} // Anything else, Let error be yes.
} else {
pError = true;
}
} // 15. If error is still no, then append a new image source to candidates whose
// URL is url, associated with a width width if not absent and a pixel
// density density if not absent. Otherwise, there is a parse error.
if (!pError) {
candidate.source = {
value: url,
startIndex: startUrlPosition
};
if (w) {
candidate.width = {
value: w
};
}
if (d) {
candidate.density = {
value: d
};
}
if (h) {
candidate.height = {
value: h
};
}
candidates.push(candidate);
} else {
throw new Error(`Invalid srcset descriptor found in '${input}' at '${desc}'`);
}
}
}
function parseSrc(input) {
if (!input) {
throw new Error('Must be non-empty');
}
let startIndex = 0;
let value = input;
while (isASCIIWhitespace(value.substring(0, 1))) {
startIndex += 1;
value = value.substring(1, value.length);
}
while (isASCIIWhitespace(value.substring(value.length - 1, value.length))) {
value = value.substring(0, value.length - 1);
}
if (!value) {
throw new Error('Must be non-empty');
}
return {
value,
startIndex
};
}
function normalizeUrl(url) {
return decodeURIComponent(url).replace(/[\t\n\r]/g, '');
}
function requestify(url, root) {
return (0, _loaderUtils.urlToRequest)(url, root);
}
function isProductionMode(loaderContext) {
return loaderContext.mode === 'production' || !loaderContext.mode;
}
const defaultMinimizerOptions = {
caseSensitive: true,
// `collapseBooleanAttributes` is not always safe, since this can break CSS attribute selectors and not safe for XHTML
collapseWhitespace: true,
conservativeCollapse: true,
keepClosingSlash: true,
// We need ability to use cssnano, or setup own function without extra dependencies
minifyCSS: true,
minifyJS: true,
// `minifyURLs` is unsafe, because we can't guarantee what the base URL is
// `removeAttributeQuotes` is not safe in some rare cases, also HTML spec recommends against doing this
removeComments: true,
// `removeEmptyAttributes` is not safe, can affect certain style or script behavior
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true // `useShortDoctype` is not safe for XHTML
};
function getMinimizeOption(rawOptions, loaderContext) {
if (typeof rawOptions.minimize === 'undefined') {
return isProductionMode(loaderContext) ? defaultMinimizerOptions : false;
}
if (typeof rawOptions.minimize === 'boolean') {
return rawOptions.minimize === true ? defaultMinimizerOptions : false;
}
return rawOptions.minimize;
}
function getAttributeValue(attributes, name) {
const lowercasedAttributes = Object.keys(attributes).reduce((keys, k) => {
// eslint-disable-next-line no-param-reassign
keys[k.toLowerCase()] = k;
return keys;
}, {});
return attributes[lowercasedAttributes[name.toLowerCase()]];
}
const defaultAttributes = [{
tag: 'audio',
attribute: 'src',
type: 'src'
}, {
tag: 'embed',
attribute: 'src',
type: 'src'
}, {
tag: 'img',
attribute: 'src',
type: 'src'
}, {
tag: 'img',
attribute: 'srcset',
type: 'srcset'
}, {
tag: 'input',
attribute: 'src',
type: 'src'
}, {
tag: 'link',
attribute: 'href',
type: 'src',
filter: (tag, attribute, attributes) => {
if (!/stylesheet/i.test(getAttributeValue(attributes, 'rel'))) {
return false;
}
if (attributes.type && getAttributeValue(attributes, 'type').trim().toLowerCase() !== 'text/css') {
return false;
}
return true;
}
}, {
tag: 'object',
attribute: 'data',
type: 'src'
}, {
tag: 'script',
attribute: 'src',
type: 'src',
filter: (tag, attribute, attributes) => {
if (attributes.type) {
const type = getAttributeValue(attributes, 'type').trim().toLowerCase();
if (type !== 'module' && type !== 'text/javascript' && type !== 'application/javascript') {
return false;
}
}
return true;
}
}, {
tag: 'source',
attribute: 'src',
type: 'src'
}, {
tag: 'source',
attribute: 'srcset',
type: 'srcset'
}, {
tag: 'track',
attribute: 'src',
type: 'src'
}, {
tag: 'video',
attribute: 'poster',
type: 'src'
}, {
tag: 'video',
attribute: 'src',
type: 'src'
}, // SVG
{
tag: 'image',
attribute: 'xlink:href',
type: 'src'
}, {
tag: 'image',
attribute: 'href',
type: 'src'
}, {
tag: 'use',
attribute: 'xlink:href',
type: 'src'
}, {
tag: 'use',
attribute: 'href',
type: 'src'
}];
function getAttributesOption(rawOptions) {
if (typeof rawOptions.attributes === 'undefined') {
return {
list: defaultAttributes
};
}
if (typeof rawOptions.attributes === 'boolean') {
return rawOptions.attributes === true ? {
list: defaultAttributes
} : false;
}
return { ...{
list: defaultAttributes
},
...rawOptions.attributes
};
}
function normalizeOptions(rawOptions, loaderContext) {
return {
preprocessor: rawOptions.preprocessor,
attributes: getAttributesOption(rawOptions),
minimize: getMinimizeOption(rawOptions, loaderContext),
esModule: typeof rawOptions.esModule === 'undefined' ? false : rawOptions.esModule
};
}
function pluginRunner(plugins) {
return {
process: content => {
const result = {
messages: []
};
const result = {};

@@ -51,19 +563,13 @@ for (const plugin of plugins) {

function isProductionMode(loaderContext) {
return loaderContext.mode === 'production' || !loaderContext.mode;
}
const GET_SOURCE_FROM_IMPORT_NAME = '___HTML_LOADER_GET_SOURCE_FROM_IMPORT___';
function getImportCode(html, importedMessages, codeOptions) {
if (importedMessages.length === 0) {
function getImportCode(html, loaderContext, imports, options) {
if (imports.length === 0) {
return '';
}
const {
loaderContext,
esModule
} = codeOptions;
const stringifiedHelperRequest = (0, _loaderUtils.stringifyRequest)(loaderContext, require.resolve('./runtime/getUrl.js'));
let code = esModule ? `import ${GET_SOURCE_FROM_IMPORT_NAME} from ${stringifiedHelperRequest};\n` : `var ${GET_SOURCE_FROM_IMPORT_NAME} = require(${stringifiedHelperRequest});\n`;
let code = options.esModule ? `import ${GET_SOURCE_FROM_IMPORT_NAME} from ${stringifiedHelperRequest};\n` : `var ${GET_SOURCE_FROM_IMPORT_NAME} = require(${stringifiedHelperRequest});\n`;
for (const item of importedMessages) {
for (const item of imports) {
const {

@@ -73,4 +579,3 @@ importName,

} = item;
const stringifiedSourceRequest = (0, _loaderUtils.stringifyRequest)(loaderContext, source);
code += esModule ? `import ${importName} from ${stringifiedSourceRequest};\n` : `var ${importName} = require(${stringifiedSourceRequest});\n`;
code += options.esModule ? `import ${importName} from ${source};\n` : `var ${importName} = require(${source});\n`;
}

@@ -81,3 +586,3 @@

function getModuleCode(html, replaceableMessages) {
function getModuleCode(html, replacements) {
let code = JSON.stringify(html) // Invalid in JavaScript but valid HTML

@@ -87,6 +592,6 @@ .replace(/[\u2028\u2029]/g, str => str === '\u2029' ? '\\u2029' : '\\u2028');

for (const item of replaceableMessages) {
for (const item of replacements) {
const {
importName,
replacerName,
replacementName,
unquoted,

@@ -97,4 +602,4 @@ hash

const preparedOptions = getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : '';
replacersCode += `var ${replacerName} = ${GET_SOURCE_FROM_IMPORT_NAME}(${importName}${preparedOptions});\n`;
code = code.replace(new RegExp(replacerName, 'g'), () => `" + ${replacerName} + "`);
replacersCode += `var ${replacementName} = ${GET_SOURCE_FROM_IMPORT_NAME}(${importName}${preparedOptions});\n`;
code = code.replace(new RegExp(replacementName, 'g'), () => `" + ${replacementName} + "`);
}

@@ -105,4 +610,4 @@

function getExportCode(html, exportedMessages, codeOptions) {
if (codeOptions.esModule) {
function getExportCode(html, options) {
if (options.esModule) {
return `// Exports\nexport default code;`;

@@ -109,0 +614,0 @@ }

{
"name": "html-loader",
"version": "1.1.0",
"version": "1.2.0",
"description": "Html loader module for webpack",

@@ -27,3 +27,3 @@ "license": "MIT",

"security": "npm audit",
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
"lint:prettier": "prettier --list-different .",
"lint:js": "eslint --cache .",

@@ -47,38 +47,36 @@ "lint": "npm-run-all -l -p \"lint:**\"",

"dependencies": {
"html-minifier-terser": "^5.0.5",
"html-minifier-terser": "^5.1.1",
"htmlparser2": "^4.1.0",
"loader-utils": "^2.0.0",
"parse-srcset": "^1.0.2",
"schema-utils": "^2.6.5"
"schema-utils": "^2.7.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/preset-env": "^7.11.0",
"@commitlint/cli": "^10.0.0",
"@commitlint/config-conventional": "^10.0.0",
"@webpack-contrib/defaults": "^6.3.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^25.2.6",
"commitlint-azure-pipelines-cli": "^1.0.3",
"babel-jest": "^26.3.0",
"cross-env": "^7.0.2",
"del": "^5.1.0",
"del-cli": "^3.0.0",
"del-cli": "^3.0.1",
"es-check": "^5.1.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-import": "^2.20.2",
"eslint": "^7.7.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0",
"file-loader": "^6.0.0",
"handlebars": "^4.7.4",
"husky": "^4.2.3",
"jest": "^25.2.6",
"jest-junit": "^10.0.0",
"lint-staged": "^10.1.1",
"memfs": "^3.1.2",
"handlebars": "^4.7.6",
"husky": "^4.2.5",
"jest": "^26.4.0",
"lint-staged": "^10.2.11",
"memfs": "^3.2.0",
"npm-run-all": "^4.1.5",
"posthtml": "^0.12.0",
"posthtml": "^0.13.2",
"posthtml-webp": "^1.5.0",
"prettier": "^2.0.2",
"standard-version": "^7.1.0",
"webpack": "^4.42.0"
"prettier": "^2.0.5",
"standard-version": "^9.0.0",
"url-loader": "^4.1.0",
"webpack": "^4.44.1"
},

@@ -85,0 +83,0 @@ "keywords": [

<div align="center">
<img width="200" height="200" src="https://worldvectorlogo.com/logos/html5.svg" alt="html-loader">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200" vspace="" hspace="25" src="https://worldvectorlogo.com/logos/webpack.svg" alt="webpack">
<img width="200" height="200" vspace="" hspace="25" src="https://webpack.js.org/assets/icon-square-big.svg" alt="webpack">
</a>

@@ -487,12 +487,16 @@ </div>

- `collapseWhitespace`
- `conservativeCollapse`
- `keepClosingSlash`
- `minifyCSS`
- `minifyJS`
- `removeAttributeQuotes`
- `removeComments`
- `removeScriptTypeAttributes`
- `removeStyleTypeAttributes`
- `useShortDoctype`
```js
({
caseSensitive: true,
collapseWhitespace: true,
conservativeCollapse: true,
keepClosingSlash: true,
minifyCSS: true,
minifyJS: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
});
```

@@ -594,23 +598,26 @@ **webpack.config.js**

**file.html**
```html
<!-- file.html -->
<img src="image.png" data-src="image2x.png" />
<img src="image.jpg" data-src="image2x.png" />
```
**index.js**
```js
require('html-loader!./file.html');
// => '<img src="http://cdn.example.com/49eba9f/a992ca.png" data-src="image2x.png">'
// => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg" data-src="image2x.png">'
```
```js
require('html-loader?attributes[]=img:data-src!./file.html');
require('html-loader?{"attributes":{"list":[{"tag":"img","attribute":"data-src","type":"src"}]}}!./file.html');
// => '<img src="image.png" data-src="data:image/png;base64,..." >'
// => '<img src="image.jpg" data-src="data:image/png;base64,..." >'
```
```js
require('html-loader?attributes[]=img:src&attributes[]=img:data-src!./file.html');
require('html-loader?{"attributes":{"list":[{"tag":"img","attribute":"src","type":"src"},{"tag":"img","attribute":"data-src","type":"src"}]}}!./file.html');
// => '<img src="http://cdn.example.com/49eba9f/a992ca.png" data-src="data:image/png;base64,..." >'
// => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg" data-src="data:image/png;base64,..." >'
```

@@ -624,9 +631,4 @@

> :warning: `-attributes` it is set attributes: false
> :warning: `-attributes` sets `attributes: false`.
```html
'<img src=http://cdn.example.com/49eba9f/a9f92ca.jpg
data-src=data:image/png;base64,...>'
```
### Process `script` and `link` tags

@@ -700,3 +702,3 @@

With the same configuration as above:
With the same configuration as in the CDN example:

@@ -720,3 +722,3 @@ **file.html**

```js
require('html-loader?root=.!./file.html');
require('html-loader?{"attributes":{"root":"."}}!./file.html');

@@ -797,3 +799,3 @@ // => '<img src="http://cdn.example.com/49eba9f/a992ca.jpg">'

options: {
preprocessor: () => {
preprocessor: (content, loaderContext) => {
let result;

@@ -864,4 +866,4 @@

[deps-url]: https://david-dm.org/webpack-contrib/html-loader
[tests]: https://dev.azure.com/webpack-contrib/html-loader/_apis/build/status/webpack-contrib.html-loader?branchName=master
[tests-url]: https://dev.azure.com/webpack-contrib/html-loader/_build/latest?definitionId=38&branchName=master
[tests]: https://github.com/webpack-contrib/html-loader/workflows/html-loader/badge.svg
[tests-url]: https://github.com/webpack-contrib/html-loader/actions
[cover]: https://codecov.io/gh/webpack-contrib/html-loader/branch/master/graph/badge.svg

@@ -868,0 +870,0 @@ [cover-url]: https://codecov.io/gh/webpack-contrib/html-loader

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc