html-loader
Advanced tools
Comparing version 1.1.0 to 1.2.0
@@ -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 @@ |
@@ -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 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63221
5
28
962
868
1
- Removedparse-srcset@^1.0.2
- Removedparse-srcset@1.0.2(transitive)
Updatedhtml-minifier-terser@^5.1.1
Updatedschema-utils@^2.7.0