feat(html/minifier): Improve removal of redundant attributes (#6197)

This commit is contained in:
Alexander Akait 2022-10-20 07:17:17 +03:00 committed by GitHub
parent 7d5b544458
commit aa3fab1957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 368 additions and 113 deletions

View File

@ -18,7 +18,7 @@ use swc_html_visit::{VisitMut, VisitMutWith};
use crate::option::{
CollapseWhitespaces, CssOptions, JsOptions, JsParserOptions, JsonOptions, MinifierType,
MinifyCssOption, MinifyJsOption, MinifyJsonOption, MinifyOptions,
MinifyCssOption, MinifyJsOption, MinifyJsonOption, MinifyOptions, RemoveRedundantAttributes,
};
pub mod option;
@ -563,44 +563,82 @@ impl Minifier<'_> {
match namespace {
Namespace::HTML | Namespace::SVG => {
// Legacy attributes, not in spec
if *tag_name == js_word!("script") {
match attribute.name {
js_word!("type") => {
let value = if let Some(next) = attribute_value.split(';').next() {
next
} else {
attribute_value
};
match value {
// Legacy JavaScript MIME types
"application/javascript"
| "application/ecmascript"
| "application/x-ecmascript"
| "application/x-javascript"
| "text/ecmascript"
| "text/javascript1.0"
| "text/javascript1.1"
| "text/javascript1.2"
| "text/javascript1.3"
| "text/javascript1.4"
| "text/javascript1.5"
| "text/jscript"
| "text/livescript"
| "text/x-ecmascript"
| "text/x-javascript" => return true,
_ => {}
match *tag_name {
js_word!("html") => match attribute.name {
js_word!("xmlns") => {
if &*attribute_value.trim().to_ascii_lowercase()
== "http://www.w3.org/1999/xhtml"
{
return true;
}
}
js_word!("xmlns:xlink") => {
if &*attribute_value.trim().to_ascii_lowercase()
== "http://www.w3.org/1999/xlink"
{
return true;
}
}
js_word!("language") => match &*attribute_value.trim().to_ascii_lowercase()
{
"javascript" | "javascript1.2" | "javascript1.3" | "javascript1.4"
| "javascript1.5" | "javascript1.6" | "javascript1.7" => return true,
_ => {}
},
_ => {}
},
js_word!("script") => {
match attribute.name {
js_word!("type") => {
let value = if let Some(next) = attribute_value.split(';').next() {
next
} else {
attribute_value
};
match value {
// Legacy JavaScript MIME types
"application/javascript"
| "application/ecmascript"
| "application/x-ecmascript"
| "application/x-javascript"
| "text/ecmascript"
| "text/javascript1.0"
| "text/javascript1.1"
| "text/javascript1.2"
| "text/javascript1.3"
| "text/javascript1.4"
| "text/javascript1.5"
| "text/jscript"
| "text/livescript"
| "text/x-ecmascript"
| "text/x-javascript" => return true,
"text/javascript" => return true,
_ => {}
}
}
js_word!("language") => {
match &*attribute_value.trim().to_ascii_lowercase() {
"javascript" | "javascript1.2" | "javascript1.3"
| "javascript1.4" | "javascript1.5" | "javascript1.6"
| "javascript1.7" => return true,
_ => {}
}
}
_ => {}
}
}
js_word!("link") => {
if attribute.name == js_word!("type")
&& &*attribute_value.trim().to_ascii_lowercase() == "text/css"
{
return true;
}
}
js_word!("svg") => {
if attribute.name == js_word!("xmlns")
&& &*attribute_value.trim().to_ascii_lowercase()
== "http://www.w3.org/2000/svg"
{
return true;
}
}
_ => {}
}
let default_attributes = if namespace == Namespace::HTML {
@ -635,7 +673,43 @@ impl Minifier<'_> {
match (attribute_info.inherited, &attribute_info.initial) {
(None, Some(initial)) | (Some(false), Some(initial)) => {
initial == normalized_value
match self.options.remove_redundant_attributes {
RemoveRedundantAttributes::None => false,
RemoveRedundantAttributes::Smart => {
if initial == normalized_value {
// It is safe to remove deprecated redundant attributes, they
// should not be used
if attribute_info.deprecated == Some(true) {
return true;
}
// It it safe to remove svg redundant attributes, they used for
// styling
if namespace == Namespace::SVG {
return true;
}
// It it safe to remove redundant attributes for metadata
// elements
if namespace == Namespace::HTML
&& matches!(
*tag_name,
js_word!("base")
| js_word!("link")
| js_word!("noscript")
| js_word!("script")
| js_word!("style")
| js_word!("title")
)
{
return true;
}
}
false
}
RemoveRedundantAttributes::All => initial == normalized_value,
}
}
_ => false,
}
@ -1607,7 +1681,7 @@ impl Minifier<'_> {
let result = child_will_be_retained(&mut child, &mut new_children, children);
if result {
if self.options.remove_redundant_attributes
if self.options.remove_empty_metadata_elements
&& self.is_empty_metadata_element(&child)
{
let need_continue = {
@ -2242,7 +2316,7 @@ impl VisitMut for Minifier<'_> {
for (i, i1) in n.attributes.iter().enumerate() {
if i1.value.is_some() {
if self.options.remove_redundant_attributes
if self.options.remove_redundant_attributes != RemoveRedundantAttributes::None
&& self.is_default_attribute_value(n.namespace, &n.tag_name, i1)
{
remove_list.push(i);

View File

@ -44,6 +44,32 @@ pub enum CollapseWhitespaces {
OnlyMetadata,
}
impl Default for CollapseWhitespaces {
fn default() -> Self {
CollapseWhitespaces::OnlyMetadata
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "kebab-case")]
pub enum RemoveRedundantAttributes {
/// Do not remove redundant attributes
None,
/// Remove all redundant attributes
All,
/// Remove deprecated and svg redundant (they used for styling) and `xmlns`
/// attributes (for example the `type` attribute for the `style` tag and
/// `xmlns` for svg)
Smart,
}
impl Default for RemoveRedundantAttributes {
fn default() -> Self {
RemoveRedundantAttributes::Smart
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
@ -116,7 +142,7 @@ pub struct CssOptions {
pub struct MinifyOptions {
#[serde(default)]
pub force_set_html5_doctype: bool,
#[serde(default = "default_collapse_whitespaces")]
#[serde(default)]
pub collapse_whitespaces: CollapseWhitespaces,
// Remove safe empty elements with metadata content, i.e. the `script` and `style` element
// without content and attributes, `meta` and `link` elements without attributes and etc
@ -136,8 +162,8 @@ pub struct MinifyOptions {
/// libraries
#[serde(default = "true_by_default")]
pub remove_empty_attributes: bool,
#[serde(default = "true_by_default")]
pub remove_redundant_attributes: bool,
#[serde(default)]
pub remove_redundant_attributes: RemoveRedundantAttributes,
#[serde(default = "true_by_default")]
pub collapse_boolean_attributes: bool,
/// Merge the same metadata elements into one (for example, consecutive
@ -184,10 +210,6 @@ impl Default for MinifyOptions {
}
}
const fn default_collapse_whitespaces() -> CollapseWhitespaces {
CollapseWhitespaces::OnlyMetadata
}
const fn true_by_default() -> bool {
true
}

View File

@ -19,7 +19,7 @@
<ul compact></ul>
<video src="" controls autoplay></video>
<script async defer></script>
<input autofocus checked disabled>
<input type=text autofocus checked disabled>
<button formnovalidate></button>
<div allowfullscreen async autofocus autoplay checked compact controls declare default defaultchecked defaultmuted defaultselected defer disabled enabled formnovalidate hidden indeterminate inert ismap itemscope loop multiple muted nohref noresize noshade novalidate nowrap open pauseonexit readonly required reversed scoped seamless selected sortable truespeed typemustmatch visible></div>

View File

@ -1,9 +1,9 @@
<!doctype html><html lang=en><title>Document</title><table width=200 border=1 align=center cellpadding=4 cellspacing=0>
<tr>
<th scope=col>Cell 1</th>
<th colspan=1 rowspan=1 scope=col>Cell 1</th>
</tr>
<tr>
<td class=test bgcolor=#FBF0DB>
<td class=test colspan=1 rowspan=1 bgcolor=#FBF0DB>
Cell 1
</td>
</tr>

View File

@ -1 +1 @@
<!doctype html><html lang=en><title>Document</title><input class=form-control id={{vm.formInputName}} name={{vm.formInputName}} placeholder=YYYY-MM-DD date-range-picker data-ng-model=vm.value data-ng-model-options="{ debounce: 1000 }" data-ng-pattern=vm.options.format data-options=vm.datepickerOptions>
<!doctype html><html lang=en><title>Document</title><input class=form-control type=text id={{vm.formInputName}} name={{vm.formInputName}} placeholder=YYYY-MM-DD date-range-picker data-ng-model=vm.value data-ng-model-options="{ debounce: 1000 }" data-ng-pattern=vm.options.format data-options=vm.datepickerOptions>

View File

@ -1,3 +0,0 @@
{
"removeRedundantAttributes": false
}

View File

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<link rel="stylesheet" href="a.css">
<link rel="stylesheet" href="b.css" type="text/css">
<link rel="stylesheet" href="b.css" type="TEXT/CSS">
<link rel="stylesheet" href="c.css" type=" text/css ">
<link rel="stylesheet" href="d.css" type="">
<link rel="stylesheet" href="d.css" type="unknown/unknown">
</head>
<body>
<div>test</div>
</body>
</html>

View File

@ -1 +0,0 @@
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=a.css><link rel=stylesheet href=b.css type=text/css><link rel=stylesheet href=b.css type=text/css><link rel=stylesheet href=c.css type=text/css><link rel=stylesheet href=d.css type=""><link rel=stylesheet href=d.css type=unknown/unknown><div>test</div>

View File

@ -1,10 +1,10 @@
<!doctype html><meta charset=utf-8><title>Test</title><form action=handler.php method=post>
<input name=str>
<input type=text name=str>
<input type=submit value=send>
</form>
<form action=handler.php>
<input name=str>
<form action=handler.php method=get>
<input type=text name=str>
<input type=submit value=send>
<input value=foo>
<input type=text value=foo>
<input type=checkbox>
</form>

View File

@ -1 +1 @@
<!doctype html><html lang=en><meta charset=UTF-8><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv=X-UA-Compatible content="ie=edge"><title>Document</title><iframe id=test src=test.html></iframe>
<!doctype html><html lang=en><meta charset=UTF-8><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv=X-UA-Compatible content="ie=edge"><title>Document</title><iframe id=test src=test.html height=150 width=300 loading=eager fetchpriority=auto referrerpolicy=strict-origin-when-cross-origin></iframe>

View File

@ -1 +1 @@
<!doctype html><html lang=en><title>Document</title><img src=test.png alt=test>
<!doctype html><html lang=en><title>Document</title><img src=test.png alt=test decoding=auto loading=eager referrerpolicy=strict-origin-when-cross-origin>

View File

@ -1,4 +1,4 @@
<!doctype html><html lang=en><title>Document</title><input name=a>
<input name=b>
<input name=c>
<input name=d>
<!doctype html><html lang=en><title>Document</title><input name=a type=text>
<input name=b type=text>
<input name=c type=text>
<input name=d size=20>

View File

@ -1,5 +1,5 @@
<!doctype html><html lang=en><title>Document</title><form action=/test>
<input onkeydown=myFunction()>
<input type=text onkeydown=myFunction()>
</form>
<div type=text onmouseover=myFunction()>test</div>
<div type=text onmouseover=myFunction()>test</div>

View File

@ -1,3 +1,3 @@
<!doctype html><html lang=en><title>Document</title><meter id=fuel max=100 low=33 high=66 optimum=80 value=50>
<!doctype html><html lang=en><title>Document</title><meter id=fuel min=0 max=100 low=33 high=66 optimum=80 value=50>
at 50/100
</meter>

View File

@ -1,11 +1,11 @@
<!doctype html><html lang=en><title>Document</title><ol>
<!doctype html><html lang=en><title>Document</title><ol type=1>
<li>Mix flour, baking powder, sugar, and salt.</li>
<li>In another bowl, mix eggs, milk, and oil.</li>
<li>Stir both mixtures together.</li>
<li>Fill muffin tray 3/4 full.</li>
<li>Bake for 20 minutes.</li>
</ol>
<ol>
<ol type=1 start=1>
<li>United Kingdom
<li>Switzerland
<li>United States

View File

@ -1 +1 @@
<!doctype html><html lang=en><title>Document</title><progress></progress>
<!doctype html><html lang=en><title>Document</title><progress max=1></progress>

View File

@ -0,0 +1,3 @@
{
"removeRedundantAttributes": "all"
}

View File

@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<link rel="stylesheet" href="a.css">
<link rel="stylesheet" href="b.css" type="text/css">
<link rel="stylesheet" href="b.css" type="TEXT/CSS">
<link rel="stylesheet" href="c.css" type=" text/css ">
<link rel="stylesheet" href="d.css" type="">
<link rel="stylesheet" href="d.css" type="unknown/unknown">
</head>
<body>
<div>test</div>
<svg xmlns="http://www.w3.org/2000/svg" width="2rem" height="2rem" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"></path>
</svg>
<form>
<div>
<label for="uname">Choose a username: </label>
<input type="text" id="uname" name="name" />
</div>
<div>
<button>Submit</button>
</div>
</form>
<form action="handler.php" method="get">
<input type="text" name="str">
<input type="submit" value="send">
<input type=" TEXT " value="foo">
<input type="checkbox">
</form>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=a.css><link rel=stylesheet href=b.css><link rel=stylesheet href=b.css><link rel=stylesheet href=c.css><link rel=stylesheet href=d.css type=""><link rel=stylesheet href=d.css type=unknown/unknown><div>test</div>
<svg width=2rem height=2rem fill=currentColor class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
<form>
<div>
<label for=uname>Choose a username: </label>
<input id=uname name=name>
</div>
<div>
<button>Submit</button>
</div>
</form>
<form action=handler.php>
<input name=str>
<input type=submit value=send>
<input value=foo>
<input type=checkbox>
</form>

View File

@ -0,0 +1,3 @@
{
"removeRedundantAttributes": "none"
}

View File

@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<link rel="stylesheet" href="a.css">
<link rel="stylesheet" href="b.css" type="text/css">
<link rel="stylesheet" href="b.css" type="TEXT/CSS">
<link rel="stylesheet" href="c.css" type=" text/css ">
<link rel="stylesheet" href="d.css" type="">
<link rel="stylesheet" href="d.css" type="unknown/unknown">
</head>
<body>
<div>test</div>
<svg xmlns="http://www.w3.org/2000/svg" width="2rem" height="2rem" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"></path>
</svg>
<form>
<div>
<label for="uname">Choose a username: </label>
<input type="text" id="uname" name="name" />
</div>
<div>
<button>Submit</button>
</div>
</form>
<form action="handler.php" method="get">
<input type="text" name="str">
<input type="submit" value="send">
<input type=" TEXT " value="foo">
<input type="checkbox">
</form>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=a.css><link rel=stylesheet href=b.css type=text/css><link rel=stylesheet href=b.css type=text/css><link rel=stylesheet href=c.css type=text/css><link rel=stylesheet href=d.css type=""><link rel=stylesheet href=d.css type=unknown/unknown><div>test</div>
<svg xmlns=http://www.w3.org/2000/svg width=2rem height=2rem fill=currentColor class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
<form>
<div>
<label for=uname>Choose a username: </label>
<input type=text id=uname name=name>
</div>
<div>
<button>Submit</button>
</div>
</form>
<form action=handler.php method=get>
<input type=text name=str>
<input type=submit value=send>
<input type=text value=foo>
<input type=checkbox>
</form>

View File

@ -0,0 +1,3 @@
{
"removeRedundantAttributes": "smart"
}

View File

@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<link rel="stylesheet" href="a.css">
<link rel="stylesheet" href="b.css" type="text/css">
<link rel="stylesheet" href="b.css" type="TEXT/CSS">
<link rel="stylesheet" href="c.css" type=" text/css ">
<link rel="stylesheet" href="d.css" type="">
<link rel="stylesheet" href="d.css" type="unknown/unknown">
</head>
<body>
<div>test</div>
<svg xmlns="http://www.w3.org/2000/svg" width="2rem" height="2rem" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"></path>
</svg>
<form>
<div>
<label for="uname">Choose a username: </label>
<input type="text" id="uname" name="name" />
</div>
<div>
<button>Submit</button>
</div>
</form>
<form action="handler.php" method="get">
<input type="text" name="str">
<input type="submit" value="send">
<input type=" TEXT " value="foo">
<input type="checkbox">
</form>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!doctype html><html lang=en><title>Document</title><link rel=stylesheet href=a.css><link rel=stylesheet href=b.css><link rel=stylesheet href=b.css><link rel=stylesheet href=c.css><link rel=stylesheet href=d.css type=""><link rel=stylesheet href=d.css type=unknown/unknown><div>test</div>
<svg width=2rem height=2rem fill=currentColor class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
<form>
<div>
<label for=uname>Choose a username: </label>
<input type=text id=uname name=name>
</div>
<div>
<button>Submit</button>
</div>
</form>
<form action=handler.php method=get>
<input type=text name=str>
<input type=submit value=send>
<input type=text value=foo>
<input type=checkbox>
</form>

View File

@ -1 +1 @@
<!doctype html><html lang=en><title>Document</title><textarea name=test id=test></textarea>
<!doctype html><html lang=en><title>Document</title><textarea name=test id=test cols=20 rows=2></textarea>

View File

@ -4,6 +4,6 @@
</video>
<video controls src=/media/cc0-videos/friday.mp4>
<track default srclang=en src=/media/examples/friday.vtt>
<track default kind=subtitles srclang=en src=/media/examples/friday.vtt>
Sorry, your browser doesn't support embedded videos.
</video>

View File

@ -1,5 +1,5 @@
<!doctype html><html lang=en><meta charset=UTF-8><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"><meta http-equiv=X-UA-Compatible content="ie=edge"><title>Document</title><map name=infographic>
<area coords=184,6,253,27 href=https://mozilla.org target=_blank alt=Mozilla>
<area shape=rect coords=184,6,253,27 href=https://mozilla.org target=_blank alt=Mozilla>
<area shape=circle coords=130,136,60 href=https://developer.mozilla.org/ target=_blank alt=MDN>
<area shape=poly coords=130,6,253,96,223,106,130,39 href=https://developer.mozilla.org/docs/Web/Guide/Graphics target=_blank alt=Graphics>
<area shape=poly coords=253,96,207,241,189,217,223,103 href=https://developer.mozilla.org/docs/Web/HTML target=_blank alt=HTML>

View File

@ -6,8 +6,8 @@
<link href=ie8only.css rel=stylesheet>
<![endif] --><div>Test</div>
<!--[if IE]><div>
<input>
<input type=text>
</div><![endif]-->
<!--[if IE]><div>
<input>
<input type=text>
</div><![endif]-->

View File

@ -1,7 +1,7 @@
<!doctype html><html lang=en><title>Document</title><form action="" class=form-example>
<!doctype html><html lang=en><title>Document</title><form action="" method=get class=form-example enctype=application/x-www-form-urlencoded>
<div class=form-example>
<label for=name>Enter your name: </label>
<input name=name id=name required>
<input type=text name=name id=name required>
</div>
<div class=form-example>
<label for=email>Enter your email: </label>
@ -14,19 +14,19 @@
<form action=# accept-charset="US-ASCII UTF-8">
First name:
<input name=fname>
<input type=text name=fname>
<br> Last name:
<input name=lname>
<input type=text name=lname>
<br>
<input type=submit value=Submit>
<input name=cc-number id=cc-number autocomplete=off>
<input name=cc-number1 id=cc-number1 autocomplete="organization organization-title">
</form>
<form action=test.php class=form-example>
<form action=test.php method=get class=form-example enctype=application/x-www-form-urlencoded>
<div class=form-example>
<label for=name>Enter your name: </label>
<input name=name id=name required>
<input type=text name=name id=name required>
</div>
<div class=form-example>
<label for=email>Enter your email: </label>
@ -34,6 +34,6 @@
</div>
<div class=form-example>
<input type=submit value=Subscribe!>
<button formaction=/action_page2.php>Submit to another page</button>
<button type=submit formaction=/action_page2.php>Submit to another page</button>
</div>
</form>

View File

@ -1 +1 @@
<!doctype html><html lang=en><meta charset=UTF-8><title>Document</title><body><template ngfor #hero [ngforof]=heroes> <hero-detail *ngif=hero [hero]=hero></hero-detail> </template><form (ngsubmit)=onSubmit(theForm) #theform=ngForm><div class=form-group><label for=name>Name</label> <input class=form-control required ngcontrol=firstName [(ngmodel)]=currentHero.firstName></div><button [disabled]=!theForm.form.valid>Submit</button></form>
<!doctype html><html lang=en><meta charset=UTF-8><title>Document</title><body><template ngfor #hero [ngforof]=heroes> <hero-detail *ngif=hero [hero]=hero></hero-detail> </template><form (ngsubmit)=onSubmit(theForm) #theform=ngForm><div class=form-group><label for=name>Name</label> <input class=form-control required ngcontrol=firstName [(ngmodel)]=currentHero.firstName></div><button type=submit [disabled]=!theForm.form.valid>Submit</button></form>

View File

@ -71,7 +71,8 @@
"body": {},
"br": {
"clear": {
"initial": "none"
"initial": "none",
"deprecated": true
}
},
"button": {
@ -174,24 +175,25 @@
"hgroup": {},
"hr": {
"align": {
"initial": "center"
"initial": "center",
"deprecated": true
},
"noshade": {
"booean": true
"booean": true,
"deprecated": true
},
"width": {
"initial": "100%"
"initial": "100%",
"deprecated": true
},
"size": {
"initial": "2"
"initial": "2",
"deprecated": true
}
},
"html": {
"xmlns": {
"initial": "http://www.w3.org/1999/xhtml"
},
"xmlns:xlink": {
"initial": "http://www.w3.org/1999/xlink"
}
},
"i": {},
@ -206,7 +208,8 @@
"initial": "auto"
},
"frameborder": {
"initial": "1"
"initial": "1",
"deprecated": true
},
"height": {
"initial": "150"
@ -221,13 +224,15 @@
"initial": "strict-origin-when-cross-origin"
},
"scrolling": {
"initial": "auto"
"initial": "auto",
"deprecated": true
},
"seamless": {
"boolean": true
},
"vspace": {
"initial": "0"
"initial": "0",
"deprecated": true
},
"width": {
"initial": "300"
@ -312,10 +317,12 @@
"mark": {},
"marquee": {
"scrollamount": {
"initial": "6"
"initial": "6",
"deprecated": true
},
"scrolldelay": {
"initial": "85"
"initial": "85",
"deprecated": true
},
"truespeed": {
"boolean": true
@ -396,7 +403,8 @@
"boolean": true
},
"charset": {
"initial": "utf-8"
"initial": "utf-8",
"deprecated": true
},
"defer": {
"boolean": true
@ -405,7 +413,8 @@
"initial": "auto"
},
"language": {
"initial": "javascript"
"initial": "javascript",
"deprecated": true
},
"nomodule": {
"boolean": true
@ -440,7 +449,8 @@
"boolean": true
},
"type": {
"initial": "text/css"
"initial": "text/css",
"deprecated": true
}
},
"sub": {},

View File

@ -12799,7 +12799,8 @@
"initial": "default"
},
"type": {
"initial": "text/css"
"initial": "text/css",
"deprecated": true
},
"media": {
"initial": "all"

View File

@ -25,6 +25,8 @@ pub struct AttributeInfo {
pub inherited: Option<bool>,
#[serde(default)]
pub boolean: Option<bool>,
#[serde(default)]
pub deprecated: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug)]