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 Heading

\\\\n

My first paragraph.

\\\\n

An Unordered HTML List

\\\\n\\\\n\\\\n\\\\n

An Ordered HTML List

\\\\n\\\\n
    \\\\n
  1. Coffee
  2. \\\\n
  3. Tea
  4. \\\\n
  5. Milk
  6. \\\\n
\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n
Foo
\\\\n\\\\n\\\\n
BAR
\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\n\\\\n \\\\n \\\\n \\\\\\"Flowers\\\\\\"\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\\\"Smiley\\\\n\\\\n
\\\\n First name:
\\\\n \\\\n
\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\nT ex t \\\\n\\\\n
\\\\n\\\\n]]>\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\nlink text\\\\n\\\\nCall me\\\\n\\\\n-->\\\\n-->\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n
\\\\n
\\\\n\\\\n<div id = "character">\\\\n© 2007\\\\nor\\\\n© 2007\\\\n\\\\n
\\\\n\\\\n\\\\\\"Red\\\\n
\\\\n Written by Jon Doe.
\\\\n Visit us at:
\\\\n Example.com
\\\\n Box 564, Disneyland
\\\\n USA\\\\n
\\\\nlink\\\\nStart Chat\\\\nStart Chat\\\\nStart Chat\\\\n\\\\n\\\\n\\\\n\\\\\\"Elva\\\\n\\\\\\"Elva\\\\n\\\\n\\\\\\"Elva\\\\n\\\\\\"Test\\\\\\"\\\\n\\\\n\\\\n\\\\n Test \\\\n\\\\ntest\\\\ntest\\\\ntest\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n

Text

\\\\n

Text

\\\\n

Text

\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\\\"Elva\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\\\"Elva\\\\n\\\\\\"test\\\\\\"/\\\\n\\\\\\"test\\\\\\"\\\\n\\\\\\"test\\\\\\"\\\\n\\\\\\"test\\\\\\"/\\\\n\\\\\\"test\\\\\\"/\\\\n\\\\n\\\\n\\\\\\"Elva\\\\n\\\\n\\\\n\\\\n\\\\n \\\\n\\\\n\\\\n\\\\n \\\\n\\\\n\\\\n\\\\n \\\\n\\\\n\\\\n\\\\n \\\\n\\\\n\\\\nfoo bar\\\\n\\\\nText\\\\nText\\\\n\\\\n\\\\n\\\\n
\\\\n\\\\nVisit our HTML tutorial\\\\nVisit our HTML tutorial\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n
text
\\\\n
text
\\\\n\\\\n\\\\n\\\\n\\\\n\\\\n\\\\\\"\\\\\\"\\\\n\\\\n\\\\\\"\\\\\\"\\\\n\\\\n\\\\\\"multi\\\\nline\\\\nalt\\\\\\"\\\\n\\\\n\\\\\\"Red\\\\n\\\\n\\\\\\" alt=\\\\\\"<%= name %>\\\\\\" src=\\\\\\"<%= imgsrc %>\\\\\\" />\\\\n\\\\n\\\\n \\\\n\\\\n\\\\n \\\\n\\\\n\\\\n \\\\n\\\\n\\\\n \\\\n\\"; +// Exports +module.exports = code;" +`; + +exports[`'attributes' option should work with "..." syntax: result 1`] = ` +" + +

My First Heading

+

My first paragraph.

+

An Unordered HTML List

+ +
    +
  • Coffee
  • +
  • Tea
  • +
  • Milk
  • +
+ +

An Ordered HTML List

+ +
    +
  1. Coffee
  2. +
  3. Tea
  4. +
  5. Milk
  6. +
+ + + + + +
Foo
+ + +
BAR
+ + + + + + + + + + + + + + + + + + + + + + + + + +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva + + + + + \\"Flowers\\" + + + + + + + + +\\"Smiley + +
+ First name:
+ +
+ + + + + + + + + + + + + +T ex t + +
+ +]]> + + + + + + + + + + + + + + + + + + + + +link text + +Call me + +--> +--> + + + + + +
+
+ +<div id = "character"> +© 2007 +or +© 2007 + +
+ +\\"Red +
+ Written by Jon Doe.
+ Visit us at:
+ Example.com
+ Box 564, Disneyland
+ USA +
+link +Start Chat +Start Chat +Start Chat + + + +\\"Elva +\\"Elva + +\\"Elva +\\"Test\\" + + + + Test + +test +test +test + + + + + +

Text

+

Text

+

Text

+ + + + + + + + + + + + + + + + + + + + +\\"Elva + + + + + + + + + + + +\\"Elva +\\"test\\"/ +\\"test\\" +\\"test\\" +\\"test\\"/ +\\"test\\"/ + + +\\"Elva + + + + + + + + + + + + + + + + + + + +foo bar + +Text +Text + + + +
+ +Visit our HTML tutorial +Visit our HTML tutorial + + + + + +
text
+
text
+ + + + + +\\"\\" + +\\"\\" + +\\"multi + +\\"Red + +\\" alt=\\"<%= name %>\\" src=\\"<%= imgsrc %>\\" /> + + + + + + + + + + + + +" +`; + +exports[`'attributes' option should work with "..." syntax: warnings 1`] = `Array []`; + exports[`'attributes' option should work with an "object" notations: errors 1`] = `Array []`; exports[`'attributes' option should work with an "object" notations: module 1`] = ` diff --git a/test/attributes-option.test.js b/test/attributes-option.test.js index f5d1f480..404b6107 100644 --- a/test/attributes-option.test.js +++ b/test/attributes-option.test.js @@ -35,6 +35,29 @@ describe("'attributes' option", () => { expect(getErrors(stats)).toMatchSnapshot('errors'); }); + it('should work with "..." syntax', async () => { + const compiler = getCompiler('simple.js', { + attributes: { + list: [ + '...', + { + tag: 'flag-icon', + attribute: 'src', + type: 'src', + }, + ], + }, + }); + const stats = await compile(compiler); + + expect(getModuleSource('./simple.html', stats)).toMatchSnapshot('module'); + expect( + execute(readAsset('main.bundle.js', compiler, stats)) + ).toMatchSnapshot('result'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + it.skip('should handle the "include" tags', async () => { const compiler = getCompiler('include.js', { attributes: { diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 0b7e6473..5a39e25c 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -47,6 +47,16 @@ describe('validate options', () => { }, ], }, + { + list: [ + '...', + { + tag: 'img', + attribute: 'srcset', + type: 'srcset', + }, + ], + }, { urlFilter: () => true }, { root: '.' }, {