Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: collapsing missing value default attribute #158

Merged
merged 2 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 80 additions & 15 deletions lib/modules/removeRedundantAttributes.es6
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const redundantAttributes = {
'charset': node => {
// The charset attribute only really makes sense on “external” SCRIPT elements:
// http://perfectionkills.com/optimizing-html/#8_script_charset
return node.attrs && ! node.attrs.src;
return node.attrs && !node.attrs.src;
}
},

Expand Down Expand Up @@ -89,7 +89,54 @@ const redundantAttributes = {
}
};

// See: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#missing-value-default
const canBeReplacedWithEmptyStringAttributes = {
audio: {
// https://html.spec.whatwg.org/#attr-media-preload
preload: 'auto'
},
video: {
preload: 'auto'
},

form: {
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute
autocomplete: 'on'
},

img: {
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decoding
decoding: 'auto'
},

track: {
// https://html.spec.whatwg.org/multipage/media.html#htmltrackelement
kind: 'subtitles'
},

textarea: {
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-wrap
wrap: 'soft'
},

area: {
// https://html.spec.whatwg.org/multipage/image-maps.html#attr-area-shape
shape: 'rect'
},

button: {
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type
type: 'submit'
},

input: {
// https://html.spec.whatwg.org/multipage/input.html#states-of-the-type-attribute
type: 'text'
}
};

const tagsHaveRedundantAttributes = new Set(Object.keys(redundantAttributes));
const tagsHaveMissingValueDefaultAttributes = new Set(Object.keys(canBeReplacedWithEmptyStringAttributes));

/** Removes redundant attributes */
export default function removeRedundantAttributes(tree) {
Expand All @@ -98,23 +145,41 @@ export default function removeRedundantAttributes(tree) {
return node;
}

if (!tagsHaveRedundantAttributes.has(node.tag)) {
return node;
}

const tagRedundantAttributes = redundantAttributes[node.tag];
node.attrs = node.attrs || {};
for (const redundantAttributeName of Object.keys(tagRedundantAttributes)) {
let tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
let isRemove = false;
if (typeof tagRedundantAttributeValue === 'function') {
isRemove = tagRedundantAttributeValue(node);
} else if (node.attrs[redundantAttributeName] === tagRedundantAttributeValue) {
isRemove = true;

if (tagsHaveRedundantAttributes.has(node.tag)) {
const tagRedundantAttributes = redundantAttributes[node.tag];

for (const redundantAttributeName of Object.keys(tagRedundantAttributes)) {
let tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
let isRemove = false;

if (typeof tagRedundantAttributeValue === 'function') {
isRemove = tagRedundantAttributeValue(node);
} else if (node.attrs[redundantAttributeName] === tagRedundantAttributeValue) {
isRemove = true;
}

if (isRemove) {
delete node.attrs[redundantAttributeName];
}
}
}

if (tagsHaveMissingValueDefaultAttributes.has(node.tag)) {
const tagMissingValueDefaultAttributes = canBeReplacedWithEmptyStringAttributes[node.tag];

if (isRemove) {
delete node.attrs[redundantAttributeName];
for (const canBeReplacedWithEmptyStringAttributeName of Object.keys(tagMissingValueDefaultAttributes)) {
let tagMissingValueDefaultAttribute = tagMissingValueDefaultAttributes[canBeReplacedWithEmptyStringAttributeName];
let isReplace = false;

if (node.attrs[canBeReplacedWithEmptyStringAttributeName] === tagMissingValueDefaultAttribute) {
isReplace = true;
}

if (isReplace) {
node.attrs[canBeReplacedWithEmptyStringAttributeName] = '';
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions test/modules/removeRedundantAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,20 @@ describe('removeRedundantAttributes', () => {
options
);
});

it('should remove preload="auto" from <audio> & <video>', () => {
return init(
'<audio src="example.com" preload="auto"></audio><video src="example.com" preload="auto"></video>',
'<audio src="example.com" preload=""></audio><video src="example.com" preload=""></video>',
options
);
});

it('should not remove preload="metadata" from <audio> & <video>', () => {
return init(
'<audio src="example.com" preload="metadata"></audio><video src="example.com" preload="metadata"></video>',
'<audio src="example.com" preload="metadata"></audio><video src="example.com" preload="metadata"></video>',
options
);
});
});