diff --git a/README.md b/README.md index 1635d3d7..af49abb5 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,17 @@ Supported tags and attributes: - the `href` attribute of the `link` tag (only for stylesheets) - the `data` attribute of the `object` tag - the `src` attribute of the `script` tag +- the `href` attribute of the `script` tag +- the `xlink:href` attribute of the `script` tag - the `src` attribute of the `source` tag - the `srcset` attribute of the `source` tag - the `src` attribute of the `track` tag - the `poster` attribute of the `video` tag - the `src` attribute of the `video` tag +- the `xlink:href` attribute of the `image` tag +- the `href` attribute of the `image` tag +- the `xlink:href` attribute of the `use` tag +- the `href` attribute of the `use` tag #### `Boolean` @@ -107,6 +113,7 @@ module.exports = { #### `Object` Allows you to specify which tags and attributes to process, filter them, filter urls and process sources starts with `/`. + For example: **webpack.config.js** @@ -121,16 +128,8 @@ module.exports = { options: { attributes: { list: [ - { - tag: 'img', - attribute: 'src', - type: 'src', - }, - { - tag: 'img', - attribute: 'srcset', - type: 'srcset', - }, + // All default supported tags and attributes + '...', { tag: 'img', attribute: 'data-src', @@ -141,26 +140,6 @@ module.exports = { attribute: 'data-srcset', type: 'srcset', }, - { - tag: 'link', - attribute: 'href', - type: 'src', - filter: (tag, attribute, attributes) => { - if (!/stylesheet/i.test(attributes.rel)) { - return false; - } - - if ( - attributes.type && - attributes.type.trim().toLowerCase() !== 'text/css' - ) { - return false; - } - - return true; - }, - }, - // More attributes ], urlFilter: (attribute, value, resourcePath) => { // The `attribute` argument contains a name of the HTML attribute. @@ -185,10 +164,12 @@ module.exports = { #### `list` Type: `Array` -Default: https://github.com/webpack-contrib/html-loader#attributes +Default: [supported tags and attributes](#attributes). Allows to setup which tags and attributes to process and how, and the ability to filter some of them. +Using `...` syntax allows you to extend [default supported tags and attributes](#attributes). + For example: **webpack.config.js** @@ -203,22 +184,8 @@ module.exports = { options: { attributes: { list: [ - { - // Tag name - tag: 'img', - // Attribute name - attribute: 'src', - // Type of processing, can be `src` or `scrset` - type: 'src', - }, - { - // Tag name - tag: 'img', - // Attribute name - attribute: 'srcset', - // Type of processing, can be `src` or `scrset` - type: 'srcset', - }, + // All default supported tags and attributes + '...', { tag: 'img', attribute: 'data-src', diff --git a/src/options.json b/src/options.json index 8291e308..099f007a 100644 --- a/src/options.json +++ b/src/options.json @@ -2,25 +2,32 @@ "type": "object", "definitions": { "Attribute": { - "type": "object", - "properties": { - "tag": { - "type": "string", - "minLength": 1 - }, - "attribute": { - "type": "string", - "minLength": 1 - }, - "type": { - "enum": ["src", "srcset"] + "anyOf": [ + { + "type": "object", + "properties": { + "tag": { + "type": "string", + "minLength": 1 + }, + "attribute": { + "type": "string", + "minLength": 1 + }, + "type": { + "enum": ["src", "srcset"] + }, + "filter": { + "instanceof": "Function" + } + }, + "required": ["attribute", "type"], + "additionalProperties": false }, - "filter": { - "instanceof": "Function" + { + "enum": ["..."] } - }, - "required": ["attribute", "type"], - "additionalProperties": false + ] }, "AttributeList": { "type": "array", diff --git a/src/utils.js b/src/utils.js index 7958bed2..3ae14daa 100644 --- a/src/utils.js +++ b/src/utils.js @@ -562,6 +562,33 @@ const defaultAttributes = [ }, ]; +function smartMergeSources(array, factory) { + if (typeof array === 'undefined') { + return factory(); + } + + const newArray = []; + + for (let i = 0; i < array.length; i++) { + const item = array[i]; + + if (item === '...') { + const items = factory(); + + if (typeof items !== 'undefined') { + // eslint-disable-next-line no-shadow + for (const item of items) { + newArray.push(item); + } + } + } else if (typeof newArray !== 'undefined') { + newArray.push(item); + } + } + + return newArray; +} + function getAttributesOption(rawOptions) { if (typeof rawOptions.attributes === 'undefined') { return { list: defaultAttributes }; @@ -571,7 +598,16 @@ function getAttributesOption(rawOptions) { return rawOptions.attributes === true ? { list: defaultAttributes } : false; } - return { ...{ list: defaultAttributes }, ...rawOptions.attributes }; + const sources = smartMergeSources( + rawOptions.attributes.list, + () => defaultAttributes + ); + + return { + list: sources, + urlFilter: rawOptions.attributes.urlFilter, + root: rawOptions.attributes.root, + }; } export function normalizeOptions(rawOptions, loaderContext) { diff --git a/test/__snapshots__/attributes-option.test.js.snap b/test/__snapshots__/attributes-option.test.js.snap index 5dade516..4ee70bd9 100644 --- a/test/__snapshots__/attributes-option.test.js.snap +++ b/test/__snapshots__/attributes-option.test.js.snap @@ -4399,6 +4399,417 @@ ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 exports[`'attributes' option should work by default: warnings 1`] = `Array []`; +exports[`'attributes' option should work with "..." syntax: errors 1`] = `Array []`; + +exports[`'attributes' option should work with "..." syntax: module 1`] = ` +"// Imports +var ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___ = require(\\"../../src/runtime/getUrl.js\\"); +var ___HTML_LOADER_IMPORT_0___ = require(\\"./image.png\\"); +var ___HTML_LOADER_IMPORT_1___ = require(\\"aliasImg\\"); +var ___HTML_LOADER_IMPORT_2___ = require(\\"./script.file.js\\"); +var ___HTML_LOADER_IMPORT_3___ = require(\\"./icons.svg\\"); +var ___HTML_LOADER_IMPORT_4___ = require(\\"./image.png?foo=bar,baz\\"); +var ___HTML_LOADER_IMPORT_5___ = require(\\"./image.png?bar=baz,foo\\"); +var ___HTML_LOADER_IMPORT_6___ = require(\\"./example.ogg\\"); +var ___HTML_LOADER_IMPORT_7___ = require(\\"./example.pdf\\"); +var ___HTML_LOADER_IMPORT_8___ = require(\\"./example.vtt\\"); +var ___HTML_LOADER_IMPORT_9___ = require(\\"./style.file.css\\"); +var ___HTML_LOADER_IMPORT_10___ = require(\\"./image image.png\\"); +var ___HTML_LOADER_IMPORT_11___ = require(\\"./module.file.js\\"); +var ___HTML_LOADER_IMPORT_12___ = require(\\"./fallback.file.js\\"); +var ___HTML_LOADER_IMPORT_13___ = require(\\"aliasImageWithSpace\\"); +var ___HTML_LOADER_IMPORT_14___ = require(\\"./webpack.svg\\"); +var ___HTML_LOADER_IMPORT_15___ = require(\\"!!url-loader!./pixel.png\\"); +// Module +var ___HTML_LOADER_REPLACEMENT_0___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___); +var ___HTML_LOADER_REPLACEMENT_1___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { maybeNeedQuotes: true }); +var ___HTML_LOADER_REPLACEMENT_2___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_1___); +var ___HTML_LOADER_REPLACEMENT_3___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_2___); +var ___HTML_LOADER_REPLACEMENT_4___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_3___, { hash: \\"#hash\\" }); +var ___HTML_LOADER_REPLACEMENT_5___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_4___); +var ___HTML_LOADER_REPLACEMENT_6___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_5___); +var ___HTML_LOADER_REPLACEMENT_7___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_6___); +var ___HTML_LOADER_REPLACEMENT_8___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_7___); +var ___HTML_LOADER_REPLACEMENT_9___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_8___); +var ___HTML_LOADER_REPLACEMENT_10___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_9___); +var ___HTML_LOADER_REPLACEMENT_11___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_10___); +var ___HTML_LOADER_REPLACEMENT_12___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_11___); +var ___HTML_LOADER_REPLACEMENT_13___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_12___); +var ___HTML_LOADER_REPLACEMENT_14___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_13___, { maybeNeedQuotes: true }); +var ___HTML_LOADER_REPLACEMENT_15___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_13___); +var ___HTML_LOADER_REPLACEMENT_16___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { hash: \\"#hash\\" }); +var ___HTML_LOADER_REPLACEMENT_17___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { hash: \\"#hash\\", maybeNeedQuotes: true }); +var ___HTML_LOADER_REPLACEMENT_18___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { hash: \\"#\\", maybeNeedQuotes: true }); +var ___HTML_LOADER_REPLACEMENT_19___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { hash: \\"#foo\\" }); +var ___HTML_LOADER_REPLACEMENT_20___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { hash: \\"#bar\\" }); +var ___HTML_LOADER_REPLACEMENT_21___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { hash: \\"#baz\\" }); +var ___HTML_LOADER_REPLACEMENT_22___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_13___, { hash: \\"#hash\\", maybeNeedQuotes: true }); +var ___HTML_LOADER_REPLACEMENT_23___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_14___); +var ___HTML_LOADER_REPLACEMENT_24___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_15___); +var code = \\"\\\\n\\\\n
My first paragraph.
\\\\n