Skip to content

Commit 9254ce2

Browse files
authored
Split markup generation from DOM property management (#10197)
* Replace SSR unit test with integration test * Remove unit test that is already covered by integration suite * Replace unit tests for boolean attrs with integration tests * Replace unit test for aria attrs with integration test * Replace unit tests for numeric 0 with integration tests * Remove unit test covered by integration tests * Replace unit test for injection with integration test It still touches internals but it tests both renderers. * Fork DOMPropertyOperations into DOMMarkupOperations * Trim down DOMPropertyOperations and DOMMarkupOperations * Record SSR sizes * Record them tests * Fix false positive warning for overloaded booleans when passing numbers * Remove stray import * Replace CSS markup tests with public API tests Some of these are handy as integration tests so I moved them there. But some test markup specifically so I changed them to use DOMServer. * Make CSSPropertyOperations client-only I forked createMarkupForStyles() into ReactDOMComponent and ReactPartialRenderer. Duplication is fine because one of them will soon be gone (guess which one!) The warnInvalidStyle helper is used by both server and client, unlike other client-only stuff in CSSPropertyOperations, so I moved it to a separately module used in both. * Record server bundle size * Add an early exit to validation * Clarify what is being duplicated
1 parent 12d5c7a commit 9254ce2

File tree

11 files changed

+742
-563
lines changed

11 files changed

+742
-563
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 130 additions & 28 deletions
Large diffs are not rendered by default.

scripts/rollup/results.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,20 @@
6565
"gzip": 81957
6666
},
6767
"react-dom-server.development.js (UMD_DEV)": {
68-
"size": 152929,
69-
"gzip": 37451
68+
"size": 119540,
69+
"gzip": 30394
7070
},
7171
"react-dom-server.production.min.js (UMD_PROD)": {
72-
"size": 25022,
73-
"gzip": 9357
72+
"size": 23004,
73+
"gzip": 8753
7474
},
7575
"react-dom-server.development.js (NODE_DEV)": {
76-
"size": 120074,
77-
"gzip": 29764
76+
"size": 88637,
77+
"gzip": 22952
7878
},
7979
"react-dom-server.production.min.js (NODE_PROD)": {
80-
"size": 22559,
81-
"gzip": 8401
80+
"size": 20744,
81+
"gzip": 7879
8282
},
8383
"ReactDOMServerStream-dev.js (FB_DEV)": {
8484
"size": 264750,
@@ -181,20 +181,20 @@
181181
"gzip": 50920
182182
},
183183
"ReactDOMServer-dev.js (FB_DEV)": {
184-
"size": 119528,
185-
"gzip": 29719
184+
"size": 88149,
185+
"gzip": 22908
186186
},
187187
"ReactDOMServer-prod.js (FB_PROD)": {
188-
"size": 60237,
189-
"gzip": 16296
188+
"size": 53879,
189+
"gzip": 14872
190190
},
191191
"react-dom-node-stream.development.js (NODE_DEV)": {
192-
"size": 121768,
193-
"gzip": 30291
192+
"size": 90331,
193+
"gzip": 23443
194194
},
195195
"react-dom-node-stream.production.min.js (NODE_PROD)": {
196-
"size": 23522,
197-
"gzip": 8727
196+
"size": 21682,
197+
"gzip": 8219
198198
},
199199
"ReactDOMNodeStream-dev.js (FB_DEV)": {
200200
"size": 264918,

src/renderers/dom/shared/CSSPropertyOperations.js

Lines changed: 2 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,13 @@
1414
var CSSProperty = require('CSSProperty');
1515
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
1616

17-
var camelizeStyleName = require('fbjs/lib/camelizeStyleName');
1817
var dangerousStyleValue = require('dangerousStyleValue');
19-
var getComponentName = require('getComponentName');
2018
var hyphenateStyleName = require('fbjs/lib/hyphenateStyleName');
21-
var memoizeStringOnly = require('fbjs/lib/memoizeStringOnly');
22-
var warning = require('fbjs/lib/warning');
2319

2420
if (__DEV__) {
25-
var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber');
21+
var warnValidStyle = require('warnValidStyle');
2622
}
2723

28-
var processStyleName = memoizeStringOnly(function(styleName) {
29-
return hyphenateStyleName(styleName);
30-
});
31-
3224
var hasShorthandPropertyBug = false;
3325
if (ExecutionEnvironment.canUseDOM) {
3426
var tempStyle = document.createElement('div').style;
@@ -40,190 +32,15 @@ if (ExecutionEnvironment.canUseDOM) {
4032
}
4133
}
4234

43-
if (__DEV__) {
44-
// 'msTransform' is correct, but the other prefixes should be capitalized
45-
var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
46-
47-
// style values shouldn't contain a semicolon
48-
var badStyleValueWithSemicolonPattern = /;\s*$/;
49-
50-
var warnedStyleNames = {};
51-
var warnedStyleValues = {};
52-
var warnedForNaNValue = false;
53-
var warnedForInfinityValue = false;
54-
55-
var warnHyphenatedStyleName = function(name, owner) {
56-
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
57-
return;
58-
}
59-
60-
warnedStyleNames[name] = true;
61-
warning(
62-
false,
63-
'Unsupported style property %s. Did you mean %s?%s',
64-
name,
65-
camelizeStyleName(name),
66-
checkRenderMessage(owner),
67-
);
68-
};
69-
70-
var warnBadVendoredStyleName = function(name, owner) {
71-
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
72-
return;
73-
}
74-
75-
warnedStyleNames[name] = true;
76-
warning(
77-
false,
78-
'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
79-
name,
80-
name.charAt(0).toUpperCase() + name.slice(1),
81-
checkRenderMessage(owner),
82-
);
83-
};
84-
85-
var warnStyleValueWithSemicolon = function(name, value, owner) {
86-
if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
87-
return;
88-
}
89-
90-
warnedStyleValues[value] = true;
91-
warning(
92-
false,
93-
"Style property values shouldn't contain a semicolon.%s " +
94-
'Try "%s: %s" instead.',
95-
checkRenderMessage(owner),
96-
name,
97-
value.replace(badStyleValueWithSemicolonPattern, ''),
98-
);
99-
};
100-
101-
var warnStyleValueIsNaN = function(name, value, owner) {
102-
if (warnedForNaNValue) {
103-
return;
104-
}
105-
106-
warnedForNaNValue = true;
107-
warning(
108-
false,
109-
'`NaN` is an invalid value for the `%s` css style property.%s',
110-
name,
111-
checkRenderMessage(owner),
112-
);
113-
};
114-
115-
var warnStyleValueIsInfinity = function(name, value, owner) {
116-
if (warnedForInfinityValue) {
117-
return;
118-
}
119-
120-
warnedForInfinityValue = true;
121-
warning(
122-
false,
123-
'`Infinity` is an invalid value for the `%s` css style property.%s',
124-
name,
125-
checkRenderMessage(owner),
126-
);
127-
};
128-
129-
var checkRenderMessage = function(owner) {
130-
var ownerName;
131-
if (owner != null) {
132-
// Stack passes the owner manually all the way to CSSPropertyOperations.
133-
ownerName = getComponentName(owner);
134-
} else {
135-
// Fiber doesn't pass it but uses ReactDebugCurrentFiber to track it.
136-
// It is only enabled in development and tracks host components too.
137-
ownerName = getCurrentFiberOwnerName();
138-
// TODO: also report the stack.
139-
}
140-
if (ownerName) {
141-
return '\n\nCheck the render method of `' + ownerName + '`.';
142-
}
143-
return '';
144-
};
145-
146-
/**
147-
* @param {string} name
148-
* @param {*} value
149-
* @param {ReactDOMComponent} component
150-
*/
151-
var warnValidStyle = function(name, value, component) {
152-
var owner;
153-
if (component) {
154-
owner = component._currentElement._owner;
155-
}
156-
if (name.indexOf('-') > -1) {
157-
warnHyphenatedStyleName(name, owner);
158-
} else if (badVendoredStyleNamePattern.test(name)) {
159-
warnBadVendoredStyleName(name, owner);
160-
} else if (badStyleValueWithSemicolonPattern.test(value)) {
161-
warnStyleValueWithSemicolon(name, value, owner);
162-
}
163-
164-
if (typeof value === 'number') {
165-
if (isNaN(value)) {
166-
warnStyleValueIsNaN(name, value, owner);
167-
} else if (!isFinite(value)) {
168-
warnStyleValueIsInfinity(name, value, owner);
169-
}
170-
}
171-
};
172-
}
173-
17435
/**
17536
* Operations for dealing with CSS properties.
17637
*/
17738
var CSSPropertyOperations = {
178-
/**
179-
* Serializes a mapping of style properties for use as inline styles:
180-
*
181-
* > createMarkupForStyles({width: '200px', height: 0})
182-
* "width:200px;height:0;"
183-
*
184-
* Undefined values are ignored so that declarative programming is easier.
185-
* The result should be HTML-escaped before insertion into the DOM.
186-
*
187-
* @param {object} styles
188-
* @param {ReactDOMComponent} component
189-
* @return {?string}
190-
*/
191-
createMarkupForStyles: function(styles, component) {
192-
var serialized = '';
193-
var delimiter = '';
194-
for (var styleName in styles) {
195-
if (!styles.hasOwnProperty(styleName)) {
196-
continue;
197-
}
198-
var isCustomProperty = styleName.indexOf('--') === 0;
199-
var styleValue = styles[styleName];
200-
if (__DEV__) {
201-
if (!isCustomProperty) {
202-
warnValidStyle(styleName, styleValue, component);
203-
}
204-
}
205-
if (styleValue != null) {
206-
serialized += delimiter + processStyleName(styleName) + ':';
207-
serialized += dangerousStyleValue(
208-
styleName,
209-
styleValue,
210-
isCustomProperty,
211-
);
212-
213-
delimiter = ';';
214-
}
215-
}
216-
return serialized || null;
217-
},
218-
21939
/**
22040
* This creates a string that is expected to be equivalent to the style
22141
* attribute generated by server-side rendering. It by-passes warnings and
22242
* security checks so it's not safe to use this value for anything other than
223-
* comparison. It is only used in DEV for SSR validation. This is duplicated
224-
* from createMarkupForStyles because createMarkupForStyles is expected to
225-
* move out of the client-side renderer and it would be nice to make a clean
226-
* break.
43+
* comparison. It is only used in DEV for SSR validation.
22744
*/
22845
createDangerousStringForStyles: function(styles) {
22946
if (__DEV__) {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule DOMMarkupOperations
10+
*/
11+
12+
'use strict';
13+
14+
var DOMProperty = require('DOMProperty');
15+
16+
var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
17+
var warning = require('fbjs/lib/warning');
18+
19+
// isAttributeNameSafe() is currently duplicated in DOMPropertyOperations.
20+
// TODO: Find a better place for this.
21+
var VALID_ATTRIBUTE_NAME_REGEX = new RegExp(
22+
'^[' +
23+
DOMProperty.ATTRIBUTE_NAME_START_CHAR +
24+
'][' +
25+
DOMProperty.ATTRIBUTE_NAME_CHAR +
26+
']*$',
27+
);
28+
var illegalAttributeNameCache = {};
29+
var validatedAttributeNameCache = {};
30+
function isAttributeNameSafe(attributeName) {
31+
if (validatedAttributeNameCache.hasOwnProperty(attributeName)) {
32+
return true;
33+
}
34+
if (illegalAttributeNameCache.hasOwnProperty(attributeName)) {
35+
return false;
36+
}
37+
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
38+
validatedAttributeNameCache[attributeName] = true;
39+
return true;
40+
}
41+
illegalAttributeNameCache[attributeName] = true;
42+
warning(false, 'Invalid attribute name: `%s`', attributeName);
43+
return false;
44+
}
45+
46+
// shouldIgnoreValue() is currently duplicated in DOMPropertyOperations.
47+
// TODO: Find a better place for this.
48+
function shouldIgnoreValue(propertyInfo, value) {
49+
return (
50+
value == null ||
51+
(propertyInfo.hasBooleanValue && !value) ||
52+
(propertyInfo.hasNumericValue && isNaN(value)) ||
53+
(propertyInfo.hasPositiveNumericValue && value < 1) ||
54+
(propertyInfo.hasOverloadedBooleanValue && value === false)
55+
);
56+
}
57+
58+
/**
59+
* Operations for dealing with DOM properties.
60+
*/
61+
var DOMMarkupOperations = {
62+
/**
63+
* Creates markup for the ID property.
64+
*
65+
* @param {string} id Unescaped ID.
66+
* @return {string} Markup string.
67+
*/
68+
createMarkupForID: function(id) {
69+
return (
70+
DOMProperty.ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id)
71+
);
72+
},
73+
74+
createMarkupForRoot: function() {
75+
return DOMProperty.ROOT_ATTRIBUTE_NAME + '=""';
76+
},
77+
78+
/**
79+
* Creates markup for a property.
80+
*
81+
* @param {string} name
82+
* @param {*} value
83+
* @return {?string} Markup string, or null if the property was invalid.
84+
*/
85+
createMarkupForProperty: function(name, value) {
86+
var propertyInfo = DOMProperty.properties.hasOwnProperty(name)
87+
? DOMProperty.properties[name]
88+
: null;
89+
if (propertyInfo) {
90+
if (shouldIgnoreValue(propertyInfo, value)) {
91+
return '';
92+
}
93+
var attributeName = propertyInfo.attributeName;
94+
if (
95+
propertyInfo.hasBooleanValue ||
96+
(propertyInfo.hasOverloadedBooleanValue && value === true)
97+
) {
98+
return attributeName + '=""';
99+
}
100+
return attributeName + '=' + quoteAttributeValueForBrowser(value);
101+
} else if (DOMProperty.isCustomAttribute(name)) {
102+
if (value == null) {
103+
return '';
104+
}
105+
return name + '=' + quoteAttributeValueForBrowser(value);
106+
}
107+
return null;
108+
},
109+
110+
/**
111+
* Creates markup for a custom property.
112+
*
113+
* @param {string} name
114+
* @param {*} value
115+
* @return {string} Markup string, or empty string if the property was invalid.
116+
*/
117+
createMarkupForCustomAttribute: function(name, value) {
118+
if (!isAttributeNameSafe(name) || value == null) {
119+
return '';
120+
}
121+
return name + '=' + quoteAttributeValueForBrowser(value);
122+
},
123+
};
124+
125+
module.exports = DOMMarkupOperations;

0 commit comments

Comments
 (0)