Skip to content

Commit f02bb48

Browse files
authoredAug 25, 2020
feat: ... syntax to extend default tag and attributes (#317)
1 parent d021e42 commit f02bb48

6 files changed

+519
-65
lines changed
 

‎README.md

+14-47
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,17 @@ Supported tags and attributes:
7575
- the `href` attribute of the `link` tag (only for stylesheets)
7676
- the `data` attribute of the `object` tag
7777
- the `src` attribute of the `script` tag
78+
- the `href` attribute of the `script` tag
79+
- the `xlink:href` attribute of the `script` tag
7880
- the `src` attribute of the `source` tag
7981
- the `srcset` attribute of the `source` tag
8082
- the `src` attribute of the `track` tag
8183
- the `poster` attribute of the `video` tag
8284
- the `src` attribute of the `video` tag
85+
- the `xlink:href` attribute of the `image` tag
86+
- the `href` attribute of the `image` tag
87+
- the `xlink:href` attribute of the `use` tag
88+
- the `href` attribute of the `use` tag
8389

8490
#### `Boolean`
8591

@@ -107,6 +113,7 @@ module.exports = {
107113
#### `Object`
108114

109115
Allows you to specify which tags and attributes to process, filter them, filter urls and process sources starts with `/`.
116+
110117
For example:
111118

112119
**webpack.config.js**
@@ -121,16 +128,8 @@ module.exports = {
121128
options: {
122129
attributes: {
123130
list: [
124-
{
125-
tag: 'img',
126-
attribute: 'src',
127-
type: 'src',
128-
},
129-
{
130-
tag: 'img',
131-
attribute: 'srcset',
132-
type: 'srcset',
133-
},
131+
// All default supported tags and attributes
132+
'...',
134133
{
135134
tag: 'img',
136135
attribute: 'data-src',
@@ -141,26 +140,6 @@ module.exports = {
141140
attribute: 'data-srcset',
142141
type: 'srcset',
143142
},
144-
{
145-
tag: 'link',
146-
attribute: 'href',
147-
type: 'src',
148-
filter: (tag, attribute, attributes) => {
149-
if (!/stylesheet/i.test(attributes.rel)) {
150-
return false;
151-
}
152-
153-
if (
154-
attributes.type &&
155-
attributes.type.trim().toLowerCase() !== 'text/css'
156-
) {
157-
return false;
158-
}
159-
160-
return true;
161-
},
162-
},
163-
// More attributes
164143
],
165144
urlFilter: (attribute, value, resourcePath) => {
166145
// The `attribute` argument contains a name of the HTML attribute.
@@ -185,10 +164,12 @@ module.exports = {
185164
#### `list`
186165

187166
Type: `Array`
188-
Default: https://github.com/webpack-contrib/html-loader#attributes
167+
Default: [supported tags and attributes](#attributes).
189168

190169
Allows to setup which tags and attributes to process and how, and the ability to filter some of them.
191170

171+
Using `...` syntax allows you to extend [default supported tags and attributes](#attributes).
172+
192173
For example:
193174

194175
**webpack.config.js**
@@ -203,22 +184,8 @@ module.exports = {
203184
options: {
204185
attributes: {
205186
list: [
206-
{
207-
// Tag name
208-
tag: 'img',
209-
// Attribute name
210-
attribute: 'src',
211-
// Type of processing, can be `src` or `scrset`
212-
type: 'src',
213-
},
214-
{
215-
// Tag name
216-
tag: 'img',
217-
// Attribute name
218-
attribute: 'srcset',
219-
// Type of processing, can be `src` or `scrset`
220-
type: 'srcset',
221-
},
187+
// All default supported tags and attributes
188+
'...',
222189
{
223190
tag: 'img',
224191
attribute: 'data-src',

‎src/options.json

+24-17
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,32 @@
22
"type": "object",
33
"definitions": {
44
"Attribute": {
5-
"type": "object",
6-
"properties": {
7-
"tag": {
8-
"type": "string",
9-
"minLength": 1
10-
},
11-
"attribute": {
12-
"type": "string",
13-
"minLength": 1
14-
},
15-
"type": {
16-
"enum": ["src", "srcset"]
5+
"anyOf": [
6+
{
7+
"type": "object",
8+
"properties": {
9+
"tag": {
10+
"type": "string",
11+
"minLength": 1
12+
},
13+
"attribute": {
14+
"type": "string",
15+
"minLength": 1
16+
},
17+
"type": {
18+
"enum": ["src", "srcset"]
19+
},
20+
"filter": {
21+
"instanceof": "Function"
22+
}
23+
},
24+
"required": ["attribute", "type"],
25+
"additionalProperties": false
1726
},
18-
"filter": {
19-
"instanceof": "Function"
27+
{
28+
"enum": ["..."]
2029
}
21-
},
22-
"required": ["attribute", "type"],
23-
"additionalProperties": false
30+
]
2431
},
2532
"AttributeList": {
2633
"type": "array",

‎src/utils.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,33 @@ const defaultAttributes = [
562562
},
563563
];
564564

565+
function smartMergeSources(array, factory) {
566+
if (typeof array === 'undefined') {
567+
return factory();
568+
}
569+
570+
const newArray = [];
571+
572+
for (let i = 0; i < array.length; i++) {
573+
const item = array[i];
574+
575+
if (item === '...') {
576+
const items = factory();
577+
578+
if (typeof items !== 'undefined') {
579+
// eslint-disable-next-line no-shadow
580+
for (const item of items) {
581+
newArray.push(item);
582+
}
583+
}
584+
} else if (typeof newArray !== 'undefined') {
585+
newArray.push(item);
586+
}
587+
}
588+
589+
return newArray;
590+
}
591+
565592
function getAttributesOption(rawOptions) {
566593
if (typeof rawOptions.attributes === 'undefined') {
567594
return { list: defaultAttributes };
@@ -571,7 +598,16 @@ function getAttributesOption(rawOptions) {
571598
return rawOptions.attributes === true ? { list: defaultAttributes } : false;
572599
}
573600

574-
return { ...{ list: defaultAttributes }, ...rawOptions.attributes };
601+
const sources = smartMergeSources(
602+
rawOptions.attributes.list,
603+
() => defaultAttributes
604+
);
605+
606+
return {
607+
list: sources,
608+
urlFilter: rawOptions.attributes.urlFilter,
609+
root: rawOptions.attributes.root,
610+
};
575611
}
576612

577613
export function normalizeOptions(rawOptions, loaderContext) {

‎test/__snapshots__/attributes-option.test.js.snap

+411
Large diffs are not rendered by default.

‎test/attributes-option.test.js

+23
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,29 @@ describe("'attributes' option", () => {
3535
expect(getErrors(stats)).toMatchSnapshot('errors');
3636
});
3737

38+
it('should work with "..." syntax', async () => {
39+
const compiler = getCompiler('simple.js', {
40+
attributes: {
41+
list: [
42+
'...',
43+
{
44+
tag: 'flag-icon',
45+
attribute: 'src',
46+
type: 'src',
47+
},
48+
],
49+
},
50+
});
51+
const stats = await compile(compiler);
52+
53+
expect(getModuleSource('./simple.html', stats)).toMatchSnapshot('module');
54+
expect(
55+
execute(readAsset('main.bundle.js', compiler, stats))
56+
).toMatchSnapshot('result');
57+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
58+
expect(getErrors(stats)).toMatchSnapshot('errors');
59+
});
60+
3861
it.skip('should handle the "include" tags', async () => {
3962
const compiler = getCompiler('include.js', {
4063
attributes: {

‎test/validate-options.test.js

+10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ describe('validate options', () => {
4747
},
4848
],
4949
},
50+
{
51+
list: [
52+
'...',
53+
{
54+
tag: 'img',
55+
attribute: 'srcset',
56+
type: 'srcset',
57+
},
58+
],
59+
},
5060
{ urlFilter: () => true },
5161
{ root: '.' },
5262
{

0 commit comments

Comments
 (0)
Please sign in to comment.