├── src
├── shared
│ ├── checkReact.js
│ ├── HTMLNodeType.js
│ ├── ReactDOMInjection.js
│ ├── voidElementTags.js
│ ├── omittedCloseTags.js
│ ├── createMicrosoftUnsafeLocalFunction.js
│ ├── isCustomComponent.js
│ ├── DOMNamespaces.js
│ ├── ReactDOMNullInputValuePropHook.js
│ ├── validAriaProperties.js
│ ├── dangerousStyleValue.js
│ ├── ReactControlledValuePropTypes.js
│ ├── assertValidProps.js
│ ├── CSSProperty.js
│ ├── CSSPropertyOperations.js
│ ├── warnValidStyle.js
│ ├── SVGDOMPropertyConfig.js
│ ├── ReactDOMInvalidARIAHook.js
│ ├── HTMLDOMPropertyConfig.js
│ ├── DOMProperty.js
│ ├── ReactDOMUnknownPropertyHook.js
│ └── possibleStandardNames.js
└── server
│ ├── ComponentCache.js
│ ├── quoteAttributeValueForBrowser.js
│ ├── ReactDOMServerNode.js
│ ├── ReactDOMStringRenderer.js
│ ├── escapeTextForBrowser.js
│ ├── DOMMarkupOperations.js
│ ├── ReactDOMNodeStreamRenderer.js
│ └── ReactPartialRenderer.js
├── package.json
└── README.md
/src/shared/checkReact.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | * @flow
8 | */
9 |
10 | import React from 'react';
11 | import invariant from 'fbjs/lib/invariant';
12 |
13 | invariant(
14 | React,
15 | 'ReactDOM was loaded before React. Make sure you load ' +
16 | 'the React package before loading ReactDOM.',
17 | );
18 |
--------------------------------------------------------------------------------
/src/shared/HTMLNodeType.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | /**
9 | * HTML nodeType values that represent the type of the node
10 | */
11 |
12 | export const ELEMENT_NODE = 1;
13 | export const TEXT_NODE = 3;
14 | export const COMMENT_NODE = 8;
15 | export const DOCUMENT_NODE = 9;
16 | export const DOCUMENT_FRAGMENT_NODE = 11;
17 |
--------------------------------------------------------------------------------
/src/shared/ReactDOMInjection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import * as DOMProperty from './DOMProperty';
9 | import HTMLDOMPropertyConfig from './HTMLDOMPropertyConfig';
10 | import SVGDOMPropertyConfig from './SVGDOMPropertyConfig';
11 |
12 | DOMProperty.injection.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
13 | DOMProperty.injection.injectDOMPropertyConfig(SVGDOMPropertyConfig);
14 |
--------------------------------------------------------------------------------
/src/shared/voidElementTags.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import omittedCloseTags from './omittedCloseTags';
9 |
10 | // For HTML, certain tags cannot have children. This has the same purpose as
11 | // `omittedCloseTags` except that `menuitem` should still have its closing tag.
12 |
13 | var voidElementTags = {
14 | menuitem: true,
15 | ...omittedCloseTags,
16 | };
17 |
18 | export default voidElementTags;
19 |
--------------------------------------------------------------------------------
/src/server/ComponentCache.js:
--------------------------------------------------------------------------------
1 | import lru from 'lru-cache';
2 |
3 | export default class ComponentCache {
4 | constructor(config = {}) {
5 |
6 | if (Number.isInteger(config)) {
7 | config = {
8 | max:config,
9 | };
10 | }
11 |
12 | this.storage = lru({
13 | max: config.max || 1000000000,
14 | length: (n, key) => {
15 | return n.length + key.length;
16 | },
17 | });
18 | }
19 |
20 | get(cacheKey, cb) {
21 | let reply = this.storage.get(cacheKey);
22 | cb(null, reply);
23 | }
24 |
25 | set(cacheKey, html) {
26 | this.storage.set(cacheKey, html);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/server/quoteAttributeValueForBrowser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import escapeTextForBrowser from './escapeTextForBrowser';
9 |
10 | /**
11 | * Escapes attribute value to prevent scripting attacks.
12 | *
13 | * @param {*} value Value to escape.
14 | * @return {string} An escaped string.
15 | */
16 | function quoteAttributeValueForBrowser(value) {
17 | return '"' + escapeTextForBrowser(value) + '"';
18 | }
19 |
20 | export default quoteAttributeValueForBrowser;
21 |
--------------------------------------------------------------------------------
/src/shared/omittedCloseTags.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | // For HTML, certain tags should omit their close tag. We keep a whitelist for
9 | // those special-case tags.
10 |
11 | var omittedCloseTags = {
12 | area: true,
13 | base: true,
14 | br: true,
15 | col: true,
16 | embed: true,
17 | hr: true,
18 | img: true,
19 | input: true,
20 | keygen: true,
21 | link: true,
22 | meta: true,
23 | param: true,
24 | source: true,
25 | track: true,
26 | wbr: true,
27 | // NOTE: menuitem's close tag should be omitted, but that causes problems.
28 | };
29 |
30 | export default omittedCloseTags;
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-component-caching",
3 | "version": "1.1.0",
4 | "description": "React Component Caching is a component-level caching library for faster server-side rendering with React 16",
5 | "main": "index.js",
6 | "scripts": {
7 | "webpack": "./node_modules/.bin/webpack -w"
8 | },
9 | "keywords": [
10 | "ssr",
11 | "react",
12 | "fiber",
13 | "server side rendering",
14 | "component caching"
15 | ],
16 | "author": {
17 | "name": "rookLab"
18 | },
19 | "license": "MIT",
20 | "dependencies": {
21 | "fbjs": "^0.8.16",
22 | "lru-cache": "^4.1.1",
23 | "object-assign": "^4.1.1",
24 | "prop-types": "^15.6.1",
25 | "react": "^16.2.0"
26 | },
27 | "devDependencies": {
28 | "webpack": "^3.5.5"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/shared/createMicrosoftUnsafeLocalFunction.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | /* globals MSApp */
9 |
10 | /**
11 | * Create a function which has 'unsafe' privileges (required by windows8 apps)
12 | */
13 | var createMicrosoftUnsafeLocalFunction = function(func) {
14 | if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) {
15 | return function(arg0, arg1, arg2, arg3) {
16 | MSApp.execUnsafeLocalFunction(function() {
17 | return func(arg0, arg1, arg2, arg3);
18 | });
19 | };
20 | } else {
21 | return func;
22 | }
23 | };
24 |
25 | export default createMicrosoftUnsafeLocalFunction;
26 |
--------------------------------------------------------------------------------
/src/server/ReactDOMServerNode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import '../shared/ReactDOMInjection';
9 | import ReactVersion from 'shared/ReactVersion';
10 |
11 | import {renderToString, renderToStaticMarkup} from './ReactDOMStringRenderer';
12 | import {
13 | renderToNodeStream,
14 | renderToStaticNodeStream,
15 | } from './ReactDOMNodeStreamRenderer';
16 |
17 | import ComponentCache from './ComponentCache';
18 |
19 |
20 | // Note: when changing this, also consider https://github.com/facebook/react/issues/11526
21 | export default {
22 | renderToString,
23 | renderToStaticMarkup,
24 | renderToNodeStream,
25 | renderToStaticNodeStream,
26 | ComponentCache,
27 | version: ReactVersion,
28 | };
29 |
--------------------------------------------------------------------------------
/src/shared/isCustomComponent.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | * @flow
8 | */
9 |
10 | function isCustomComponent(tagName: string, props: Object) {
11 | if (tagName.indexOf('-') === -1) {
12 | return typeof props.is === 'string';
13 | }
14 | switch (tagName) {
15 | // These are reserved SVG and MathML elements.
16 | // We don't mind this whitelist too much because we expect it to never grow.
17 | // The alternative is to track the namespace in a few places which is convoluted.
18 | // https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
19 | case 'annotation-xml':
20 | case 'color-profile':
21 | case 'font-face':
22 | case 'font-face-src':
23 | case 'font-face-uri':
24 | case 'font-face-format':
25 | case 'font-face-name':
26 | case 'missing-glyph':
27 | return false;
28 | default:
29 | return true;
30 | }
31 | }
32 |
33 | export default isCustomComponent;
34 |
--------------------------------------------------------------------------------
/src/server/ReactDOMStringRenderer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import ReactPartialRenderer from './ReactPartialRenderer';
9 |
10 | /**
11 | * Render a ReactElement to its initial HTML. This should only be used on the
12 | * server.
13 | * See https://reactjs.org/docs/react-dom-server.html#rendertostring
14 | */
15 | export async function renderToString(element, cache, memLife=0) {
16 | // If and only if using memcached, pass the lifetime of your cache entry (in seconds) into 'memLife'.
17 | var renderer = new ReactPartialRenderer(element, false);
18 | var markup = await renderer.read(Infinity, cache, false, null, memLife);
19 | return markup;
20 | }
21 |
22 | /**
23 | * Similar to renderToString, except this doesn't create extra DOM attributes
24 | * such as data-react-id that React uses internally.
25 | * See https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
26 | */
27 | export async function renderToStaticMarkup(element, cache, memLife=0) {
28 | var renderer = new ReactPartialRenderer(element, true);
29 | var markup = await renderer.read(Infinity, cache, false, null, memLife);
30 | return markup;
31 | }
32 |
--------------------------------------------------------------------------------
/src/shared/DOMNamespaces.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
9 | const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
10 | const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
11 |
12 | export const Namespaces = {
13 | html: HTML_NAMESPACE,
14 | mathml: MATH_NAMESPACE,
15 | svg: SVG_NAMESPACE,
16 | };
17 |
18 | // Assumes there is no parent namespace.
19 | export function getIntrinsicNamespace(type: string): string {
20 | switch (type) {
21 | case 'svg':
22 | return SVG_NAMESPACE;
23 | case 'math':
24 | return MATH_NAMESPACE;
25 | default:
26 | return HTML_NAMESPACE;
27 | }
28 | }
29 |
30 | export function getChildNamespace(
31 | parentNamespace: string | null,
32 | type: string,
33 | ): string {
34 | if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
35 | // No (or default) parent namespace: potential entry point.
36 | return getIntrinsicNamespace(type);
37 | }
38 | if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
39 | // We're leaving SVG.
40 | return HTML_NAMESPACE;
41 | }
42 | // By default, pass namespace below.
43 | return parentNamespace;
44 | }
45 |
--------------------------------------------------------------------------------
/src/shared/ReactDOMNullInputValuePropHook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
9 | import warning from 'fbjs/lib/warning';
10 |
11 | var didWarnValueNull = false;
12 |
13 | function getStackAddendum() {
14 | var stack = ReactDebugCurrentFrame.getStackAddendum();
15 | return stack != null ? stack : '';
16 | }
17 |
18 | export function validateProperties(type, props) {
19 | if (type !== 'input' && type !== 'textarea' && type !== 'select') {
20 | return;
21 | }
22 |
23 | if (props != null && props.value === null && !didWarnValueNull) {
24 | didWarnValueNull = true;
25 | if (type === 'select' && props.multiple) {
26 | warning(
27 | false,
28 | '`value` prop on `%s` should not be null. ' +
29 | 'Consider using an empty array when `multiple` is set to `true` ' +
30 | 'to clear the component or `undefined` for uncontrolled components.%s',
31 | type,
32 | getStackAddendum(),
33 | );
34 | } else {
35 | warning(
36 | false,
37 | '`value` prop on `%s` should not be null. ' +
38 | 'Consider using an empty string to clear the component or `undefined` ' +
39 | 'for uncontrolled components.%s',
40 | type,
41 | getStackAddendum(),
42 | );
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/shared/validAriaProperties.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | var ariaProperties = {
9 | 'aria-current': 0, // state
10 | 'aria-details': 0,
11 | 'aria-disabled': 0, // state
12 | 'aria-hidden': 0, // state
13 | 'aria-invalid': 0, // state
14 | 'aria-keyshortcuts': 0,
15 | 'aria-label': 0,
16 | 'aria-roledescription': 0,
17 | // Widget Attributes
18 | 'aria-autocomplete': 0,
19 | 'aria-checked': 0,
20 | 'aria-expanded': 0,
21 | 'aria-haspopup': 0,
22 | 'aria-level': 0,
23 | 'aria-modal': 0,
24 | 'aria-multiline': 0,
25 | 'aria-multiselectable': 0,
26 | 'aria-orientation': 0,
27 | 'aria-placeholder': 0,
28 | 'aria-pressed': 0,
29 | 'aria-readonly': 0,
30 | 'aria-required': 0,
31 | 'aria-selected': 0,
32 | 'aria-sort': 0,
33 | 'aria-valuemax': 0,
34 | 'aria-valuemin': 0,
35 | 'aria-valuenow': 0,
36 | 'aria-valuetext': 0,
37 | // Live Region Attributes
38 | 'aria-atomic': 0,
39 | 'aria-busy': 0,
40 | 'aria-live': 0,
41 | 'aria-relevant': 0,
42 | // Drag-and-Drop Attributes
43 | 'aria-dropeffect': 0,
44 | 'aria-grabbed': 0,
45 | // Relationship Attributes
46 | 'aria-activedescendant': 0,
47 | 'aria-colcount': 0,
48 | 'aria-colindex': 0,
49 | 'aria-colspan': 0,
50 | 'aria-controls': 0,
51 | 'aria-describedby': 0,
52 | 'aria-errormessage': 0,
53 | 'aria-flowto': 0,
54 | 'aria-labelledby': 0,
55 | 'aria-owns': 0,
56 | 'aria-posinset': 0,
57 | 'aria-rowcount': 0,
58 | 'aria-rowindex': 0,
59 | 'aria-rowspan': 0,
60 | 'aria-setsize': 0,
61 | };
62 |
63 | export default ariaProperties;
64 |
--------------------------------------------------------------------------------
/src/shared/dangerousStyleValue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {isUnitlessNumber} from './CSSProperty';
9 |
10 | /**
11 | * Convert a value into the proper css writable value. The style name `name`
12 | * should be logical (no hyphens), as specified
13 | * in `CSSProperty.isUnitlessNumber`.
14 | *
15 | * @param {string} name CSS property name such as `topMargin`.
16 | * @param {*} value CSS property value such as `10px`.
17 | * @return {string} Normalized style value with dimensions applied.
18 | */
19 | function dangerousStyleValue(name, value, isCustomProperty) {
20 | // Note that we've removed escapeTextForBrowser() calls here since the
21 | // whole string will be escaped when the attribute is injected into
22 | // the markup. If you provide unsafe user data here they can inject
23 | // arbitrary CSS which may be problematic (I couldn't repro this):
24 | // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
25 | // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
26 | // This is not an XSS hole but instead a potential CSS injection issue
27 | // which has lead to a greater discussion about how we're going to
28 | // trust URLs moving forward. See #2115901
29 |
30 | var isEmpty = value == null || typeof value === 'boolean' || value === '';
31 | if (isEmpty) {
32 | return '';
33 | }
34 |
35 | if (
36 | !isCustomProperty &&
37 | typeof value === 'number' &&
38 | value !== 0 &&
39 | !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])
40 | ) {
41 | return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers
42 | }
43 |
44 | return ('' + value).trim();
45 | }
46 |
47 | export default dangerousStyleValue;
48 |
--------------------------------------------------------------------------------
/src/shared/ReactControlledValuePropTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import checkPropTypes from 'prop-types/checkPropTypes';
9 |
10 | var ReactControlledValuePropTypes = {
11 | checkPropTypes: null,
12 | };
13 |
14 | if (__DEV__) {
15 | var hasReadOnlyValue = {
16 | button: true,
17 | checkbox: true,
18 | image: true,
19 | hidden: true,
20 | radio: true,
21 | reset: true,
22 | submit: true,
23 | };
24 |
25 | var propTypes = {
26 | value: function(props, propName, componentName) {
27 | if (
28 | !props[propName] ||
29 | hasReadOnlyValue[props.type] ||
30 | props.onChange ||
31 | props.readOnly ||
32 | props.disabled
33 | ) {
34 | return null;
35 | }
36 | return new Error(
37 | 'You provided a `value` prop to a form field without an ' +
38 | '`onChange` handler. This will render a read-only field. If ' +
39 | 'the field should be mutable use `defaultValue`. Otherwise, ' +
40 | 'set either `onChange` or `readOnly`.',
41 | );
42 | },
43 | checked: function(props, propName, componentName) {
44 | if (
45 | !props[propName] ||
46 | props.onChange ||
47 | props.readOnly ||
48 | props.disabled
49 | ) {
50 | return null;
51 | }
52 | return new Error(
53 | 'You provided a `checked` prop to a form field without an ' +
54 | '`onChange` handler. This will render a read-only field. If ' +
55 | 'the field should be mutable use `defaultChecked`. Otherwise, ' +
56 | 'set either `onChange` or `readOnly`.',
57 | );
58 | },
59 | };
60 |
61 | /**
62 | * Provide a linked `value` attribute for controlled forms. You should not use
63 | * this outside of the ReactDOM controlled form components.
64 | */
65 | ReactControlledValuePropTypes.checkPropTypes = function(
66 | tagName,
67 | props,
68 | getStack,
69 | ) {
70 | checkPropTypes(propTypes, props, 'prop', tagName, getStack);
71 | };
72 | }
73 |
74 | export default ReactControlledValuePropTypes;
75 |
--------------------------------------------------------------------------------
/src/shared/assertValidProps.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import invariant from 'fbjs/lib/invariant';
9 | import warning from 'fbjs/lib/warning';
10 |
11 | import voidElementTags from './voidElementTags';
12 |
13 | var HTML = '__html';
14 |
15 | function assertValidProps(tag: string, props: ?Object, getStack: () => string) {
16 | if (!props) {
17 | return;
18 | }
19 | // Note the use of `==` which checks for null or undefined.
20 | if (voidElementTags[tag]) {
21 | invariant(
22 | props.children == null && props.dangerouslySetInnerHTML == null,
23 | '%s is a void element tag and must neither have `children` nor ' +
24 | 'use `dangerouslySetInnerHTML`.%s',
25 | tag,
26 | getStack(),
27 | );
28 | }
29 | if (props.dangerouslySetInnerHTML != null) {
30 | invariant(
31 | props.children == null,
32 | 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
33 | );
34 | invariant(
35 | typeof props.dangerouslySetInnerHTML === 'object' &&
36 | HTML in props.dangerouslySetInnerHTML,
37 | '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
38 | 'Please visit https://fb.me/react-invariant-dangerously-set-inner-html ' +
39 | 'for more information.',
40 | );
41 | }
42 | if (__DEV__) {
43 | warning(
44 | props.suppressContentEditableWarning ||
45 | !props.contentEditable ||
46 | props.children == null,
47 | 'A component is `contentEditable` and contains `children` managed by ' +
48 | 'React. It is now your responsibility to guarantee that none of ' +
49 | 'those nodes are unexpectedly modified or duplicated. This is ' +
50 | 'probably not intentional.%s',
51 | getStack(),
52 | );
53 | }
54 | invariant(
55 | props.style == null || typeof props.style === 'object',
56 | 'The `style` prop expects a mapping from style properties to values, ' +
57 | "not a string. For example, style={{marginRight: spacing + 'em'}} when " +
58 | 'using JSX.%s',
59 | getStack(),
60 | );
61 | }
62 |
63 | export default assertValidProps;
64 |
--------------------------------------------------------------------------------
/src/shared/CSSProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | /**
9 | * CSS properties which accept numbers but are not in units of "px".
10 | */
11 | export const isUnitlessNumber = {
12 | animationIterationCount: true,
13 | borderImageOutset: true,
14 | borderImageSlice: true,
15 | borderImageWidth: true,
16 | boxFlex: true,
17 | boxFlexGroup: true,
18 | boxOrdinalGroup: true,
19 | columnCount: true,
20 | columns: true,
21 | flex: true,
22 | flexGrow: true,
23 | flexPositive: true,
24 | flexShrink: true,
25 | flexNegative: true,
26 | flexOrder: true,
27 | gridRow: true,
28 | gridRowEnd: true,
29 | gridRowSpan: true,
30 | gridRowStart: true,
31 | gridColumn: true,
32 | gridColumnEnd: true,
33 | gridColumnSpan: true,
34 | gridColumnStart: true,
35 | fontWeight: true,
36 | lineClamp: true,
37 | lineHeight: true,
38 | opacity: true,
39 | order: true,
40 | orphans: true,
41 | tabSize: true,
42 | widows: true,
43 | zIndex: true,
44 | zoom: true,
45 |
46 | // SVG-related properties
47 | fillOpacity: true,
48 | floodOpacity: true,
49 | stopOpacity: true,
50 | strokeDasharray: true,
51 | strokeDashoffset: true,
52 | strokeMiterlimit: true,
53 | strokeOpacity: true,
54 | strokeWidth: true,
55 | };
56 |
57 | /**
58 | * @param {string} prefix vendor-specific prefix, eg: Webkit
59 | * @param {string} key style name, eg: transitionDuration
60 | * @return {string} style name prefixed with `prefix`, properly camelCased, eg:
61 | * WebkitTransitionDuration
62 | */
63 | function prefixKey(prefix, key) {
64 | return prefix + key.charAt(0).toUpperCase() + key.substring(1);
65 | }
66 |
67 | /**
68 | * Support style names that may come passed in prefixed by adding permutations
69 | * of vendor prefixes.
70 | */
71 | var prefixes = ['Webkit', 'ms', 'Moz', 'O'];
72 |
73 | // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
74 | // infinite loop, because it iterates over the newly added props too.
75 | Object.keys(isUnitlessNumber).forEach(function(prop) {
76 | prefixes.forEach(function(prefix) {
77 | isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/src/shared/CSSPropertyOperations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import dangerousStyleValue from './dangerousStyleValue';
9 | import hyphenateStyleName from 'fbjs/lib/hyphenateStyleName';
10 | import warnValidStyle from './warnValidStyle';
11 |
12 | /**
13 | * Operations for dealing with CSS properties.
14 | */
15 |
16 | /**
17 | * This creates a string that is expected to be equivalent to the style
18 | * attribute generated by server-side rendering. It by-passes warnings and
19 | * security checks so it's not safe to use this value for anything other than
20 | * comparison. It is only used in DEV for SSR validation.
21 | */
22 | export function createDangerousStringForStyles(styles) {
23 | if (__DEV__) {
24 | var serialized = '';
25 | var delimiter = '';
26 | for (var styleName in styles) {
27 | if (!styles.hasOwnProperty(styleName)) {
28 | continue;
29 | }
30 | var styleValue = styles[styleName];
31 | if (styleValue != null) {
32 | var isCustomProperty = styleName.indexOf('--') === 0;
33 | serialized += delimiter + hyphenateStyleName(styleName) + ':';
34 | serialized += dangerousStyleValue(
35 | styleName,
36 | styleValue,
37 | isCustomProperty,
38 | );
39 |
40 | delimiter = ';';
41 | }
42 | }
43 | return serialized || null;
44 | }
45 | }
46 |
47 | /**
48 | * Sets the value for multiple styles on a node. If a value is specified as
49 | * '' (empty string), the corresponding style property will be unset.
50 | *
51 | * @param {DOMElement} node
52 | * @param {object} styles
53 | */
54 | export function setValueForStyles(node, styles, getStack) {
55 | var style = node.style;
56 | for (var styleName in styles) {
57 | if (!styles.hasOwnProperty(styleName)) {
58 | continue;
59 | }
60 | var isCustomProperty = styleName.indexOf('--') === 0;
61 | if (__DEV__) {
62 | if (!isCustomProperty) {
63 | warnValidStyle(styleName, styles[styleName], getStack);
64 | }
65 | }
66 | var styleValue = dangerousStyleValue(
67 | styleName,
68 | styles[styleName],
69 | isCustomProperty,
70 | );
71 | if (styleName === 'float') {
72 | styleName = 'cssFloat';
73 | }
74 | if (isCustomProperty) {
75 | style.setProperty(styleName, styleValue);
76 | } else {
77 | style[styleName] = styleValue;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/shared/warnValidStyle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import emptyFunction from 'fbjs/lib/emptyFunction';
9 | import camelizeStyleName from 'fbjs/lib/camelizeStyleName';
10 | import warning from 'fbjs/lib/warning';
11 |
12 | var warnValidStyle = emptyFunction;
13 |
14 | if (__DEV__) {
15 | // 'msTransform' is correct, but the other prefixes should be capitalized
16 | var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
17 |
18 | // style values shouldn't contain a semicolon
19 | var badStyleValueWithSemicolonPattern = /;\s*$/;
20 |
21 | var warnedStyleNames = {};
22 | var warnedStyleValues = {};
23 | var warnedForNaNValue = false;
24 | var warnedForInfinityValue = false;
25 |
26 | var warnHyphenatedStyleName = function(name, getStack) {
27 | if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
28 | return;
29 | }
30 |
31 | warnedStyleNames[name] = true;
32 | warning(
33 | false,
34 | 'Unsupported style property %s. Did you mean %s?%s',
35 | name,
36 | camelizeStyleName(name),
37 | getStack(),
38 | );
39 | };
40 |
41 | var warnBadVendoredStyleName = function(name, getStack) {
42 | if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
43 | return;
44 | }
45 |
46 | warnedStyleNames[name] = true;
47 | warning(
48 | false,
49 | 'Unsupported vendor-prefixed style property %s. Did you mean %s?%s',
50 | name,
51 | name.charAt(0).toUpperCase() + name.slice(1),
52 | getStack(),
53 | );
54 | };
55 |
56 | var warnStyleValueWithSemicolon = function(name, value, getStack) {
57 | if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
58 | return;
59 | }
60 |
61 | warnedStyleValues[value] = true;
62 | warning(
63 | false,
64 | "Style property values shouldn't contain a semicolon. " +
65 | 'Try "%s: %s" instead.%s',
66 | name,
67 | value.replace(badStyleValueWithSemicolonPattern, ''),
68 | getStack(),
69 | );
70 | };
71 |
72 | var warnStyleValueIsNaN = function(name, value, getStack) {
73 | if (warnedForNaNValue) {
74 | return;
75 | }
76 |
77 | warnedForNaNValue = true;
78 | warning(
79 | false,
80 | '`NaN` is an invalid value for the `%s` css style property.%s',
81 | name,
82 | getStack(),
83 | );
84 | };
85 |
86 | var warnStyleValueIsInfinity = function(name, value, getStack) {
87 | if (warnedForInfinityValue) {
88 | return;
89 | }
90 |
91 | warnedForInfinityValue = true;
92 | warning(
93 | false,
94 | '`Infinity` is an invalid value for the `%s` css style property.%s',
95 | name,
96 | getStack(),
97 | );
98 | };
99 |
100 | warnValidStyle = function(name, value, getStack) {
101 | if (name.indexOf('-') > -1) {
102 | warnHyphenatedStyleName(name, getStack);
103 | } else if (badVendoredStyleNamePattern.test(name)) {
104 | warnBadVendoredStyleName(name, getStack);
105 | } else if (badStyleValueWithSemicolonPattern.test(value)) {
106 | warnStyleValueWithSemicolon(name, value, getStack);
107 | }
108 |
109 | if (typeof value === 'number') {
110 | if (isNaN(value)) {
111 | warnStyleValueIsNaN(name, value, getStack);
112 | } else if (!isFinite(value)) {
113 | warnStyleValueIsInfinity(name, value, getStack);
114 | }
115 | }
116 | };
117 | }
118 |
119 | export default warnValidStyle;
120 |
--------------------------------------------------------------------------------
/src/server/escapeTextForBrowser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | * Based on the escape-html library, which is used under the MIT License below:
8 | *
9 | * Copyright (c) 2012-2013 TJ Holowaychuk
10 | * Copyright (c) 2015 Andreas Lubbe
11 | * Copyright (c) 2015 Tiancheng "Timothy" Gu
12 | *
13 | * Permission is hereby granted, free of charge, to any person obtaining
14 | * a copy of this software and associated documentation files (the
15 | * 'Software'), to deal in the Software without restriction, including
16 | * without limitation the rights to use, copy, modify, merge, publish,
17 | * distribute, sublicense, and/or sell copies of the Software, and to
18 | * permit persons to whom the Software is furnished to do so, subject to
19 | * the following conditions:
20 | *
21 | * The above copyright notice and this permission notice shall be
22 | * included in all copies or substantial portions of the Software.
23 | *
24 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
28 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
29 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
30 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 | */
32 |
33 | // code copied and modified from escape-html
34 | /**
35 | * Module variables.
36 | * @private
37 | */
38 |
39 | var matchHtmlRegExp = /["'&<>]/;
40 |
41 | /**
42 | * Escapes special characters and HTML entities in a given html string.
43 | *
44 | * @param {string} string HTML string to escape for later insertion
45 | * @return {string}
46 | * @public
47 | */
48 |
49 | function escapeHtml(string) {
50 | var str = '' + string;
51 | var match = matchHtmlRegExp.exec(str);
52 |
53 | if (!match) {
54 | return str;
55 | }
56 |
57 | var escape;
58 | var html = '';
59 | var index = 0;
60 | var lastIndex = 0;
61 |
62 | for (index = match.index; index < str.length; index++) {
63 | switch (str.charCodeAt(index)) {
64 | case 34: // "
65 | escape = '"';
66 | break;
67 | case 38: // &
68 | escape = '&';
69 | break;
70 | case 39: // '
71 | escape = '''; // modified from escape-html; used to be '''
72 | break;
73 | case 60: // <
74 | escape = '<';
75 | break;
76 | case 62: // >
77 | escape = '>';
78 | break;
79 | default:
80 | continue;
81 | }
82 |
83 | if (lastIndex !== index) {
84 | html += str.substring(lastIndex, index);
85 | }
86 |
87 | lastIndex = index + 1;
88 | html += escape;
89 | }
90 |
91 | return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
92 | }
93 | // end code copied and modified from escape-html
94 |
95 | /**
96 | * Escapes text to prevent scripting attacks.
97 | *
98 | * @param {*} text Text value to escape.
99 | * @return {string} An escaped string.
100 | */
101 | function escapeTextForBrowser(text) {
102 | if (typeof text === 'boolean' || typeof text === 'number') {
103 | // this shortcircuit helps perf for types that we know will never have
104 | // special characters, especially given that this function is used often
105 | // for numeric dom ids.
106 | return '' + text;
107 | }
108 | return escapeHtml(text);
109 | }
110 |
111 | export default escapeTextForBrowser;
112 |
--------------------------------------------------------------------------------
/src/server/DOMMarkupOperations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {
9 | ATTRIBUTE_NAME_CHAR,
10 | ATTRIBUTE_NAME_START_CHAR,
11 | ID_ATTRIBUTE_NAME,
12 | ROOT_ATTRIBUTE_NAME,
13 | getPropertyInfo,
14 | shouldAttributeAcceptBooleanValue,
15 | shouldSetAttribute,
16 | } from '../shared/DOMProperty';
17 | import quoteAttributeValueForBrowser from './quoteAttributeValueForBrowser';
18 | import warning from 'fbjs/lib/warning';
19 |
20 | // isAttributeNameSafe() is currently duplicated in DOMPropertyOperations.
21 | // TODO: Find a better place for this.
22 | var VALID_ATTRIBUTE_NAME_REGEX = new RegExp(
23 | '^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$',
24 | );
25 | var illegalAttributeNameCache = {};
26 | var validatedAttributeNameCache = {};
27 | function isAttributeNameSafe(attributeName) {
28 | if (validatedAttributeNameCache.hasOwnProperty(attributeName)) {
29 | return true;
30 | }
31 | if (illegalAttributeNameCache.hasOwnProperty(attributeName)) {
32 | return false;
33 | }
34 | if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
35 | validatedAttributeNameCache[attributeName] = true;
36 | return true;
37 | }
38 | illegalAttributeNameCache[attributeName] = true;
39 | if (__DEV__) {
40 | warning(false, 'Invalid attribute name: `%s`', attributeName);
41 | }
42 | return false;
43 | }
44 |
45 | // shouldIgnoreValue() is currently duplicated in DOMPropertyOperations.
46 | // TODO: Find a better place for this.
47 | function shouldIgnoreValue(propertyInfo, value) {
48 | return (
49 | value == null ||
50 | (propertyInfo.hasBooleanValue && !value) ||
51 | (propertyInfo.hasNumericValue && isNaN(value)) ||
52 | (propertyInfo.hasPositiveNumericValue && value < 1) ||
53 | (propertyInfo.hasOverloadedBooleanValue && value === false)
54 | );
55 | }
56 |
57 | /**
58 | * Operations for dealing with DOM properties.
59 | */
60 |
61 | /**
62 | * Creates markup for the ID property.
63 | *
64 | * @param {string} id Unescaped ID.
65 | * @return {string} Markup string.
66 | */
67 | export function createMarkupForID(id) {
68 | return ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id);
69 | }
70 |
71 | export function createMarkupForRoot() {
72 | return ROOT_ATTRIBUTE_NAME + '=""';
73 | }
74 |
75 | /**
76 | * Creates markup for a property.
77 | *
78 | * @param {string} name
79 | * @param {*} value
80 | * @return {?string} Markup string, or null if the property was invalid.
81 | */
82 | export function createMarkupForProperty(name, value) {
83 | var propertyInfo = getPropertyInfo(name);
84 | if (propertyInfo) {
85 | if (shouldIgnoreValue(propertyInfo, value)) {
86 | return '';
87 | }
88 | var attributeName = propertyInfo.attributeName;
89 | if (
90 | propertyInfo.hasBooleanValue ||
91 | (propertyInfo.hasOverloadedBooleanValue && value === true)
92 | ) {
93 | return attributeName + '=""';
94 | } else if (
95 | typeof value !== 'boolean' ||
96 | shouldAttributeAcceptBooleanValue(name)
97 | ) {
98 | return attributeName + '=' + quoteAttributeValueForBrowser(value);
99 | }
100 | } else if (shouldSetAttribute(name, value)) {
101 | if (value == null) {
102 | return '';
103 | }
104 | return name + '=' + quoteAttributeValueForBrowser(value);
105 | }
106 | return null;
107 | }
108 |
109 | /**
110 | * Creates markup for a custom property.
111 | *
112 | * @param {string} name
113 | * @param {*} value
114 | * @return {string} Markup string, or empty string if the property was invalid.
115 | */
116 | export function createMarkupForCustomAttribute(name, value) {
117 | if (!isAttributeNameSafe(name) || value == null) {
118 | return '';
119 | }
120 | return name + '=' + quoteAttributeValueForBrowser(value);
121 | }
122 |
--------------------------------------------------------------------------------
/src/shared/SVGDOMPropertyConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {injection} from './DOMProperty';
9 |
10 | var {HAS_STRING_BOOLEAN_VALUE} = injection;
11 |
12 | var NS = {
13 | xlink: 'http://www.w3.org/1999/xlink',
14 | xml: 'http://www.w3.org/XML/1998/namespace',
15 | };
16 |
17 | /**
18 | * This is a list of all SVG attributes that need special casing,
19 | * namespacing, or boolean value assignment.
20 | *
21 | * When adding attributes to this list, be sure to also add them to
22 | * the `possibleStandardNames` module to ensure casing and incorrect
23 | * name warnings.
24 | *
25 | * SVG Attributes List:
26 | * https://www.w3.org/TR/SVG/attindex.html
27 | * SMIL Spec:
28 | * https://www.w3.org/TR/smil
29 | */
30 | var ATTRS = [
31 | 'accent-height',
32 | 'alignment-baseline',
33 | 'arabic-form',
34 | 'baseline-shift',
35 | 'cap-height',
36 | 'clip-path',
37 | 'clip-rule',
38 | 'color-interpolation',
39 | 'color-interpolation-filters',
40 | 'color-profile',
41 | 'color-rendering',
42 | 'dominant-baseline',
43 | 'enable-background',
44 | 'fill-opacity',
45 | 'fill-rule',
46 | 'flood-color',
47 | 'flood-opacity',
48 | 'font-family',
49 | 'font-size',
50 | 'font-size-adjust',
51 | 'font-stretch',
52 | 'font-style',
53 | 'font-variant',
54 | 'font-weight',
55 | 'glyph-name',
56 | 'glyph-orientation-horizontal',
57 | 'glyph-orientation-vertical',
58 | 'horiz-adv-x',
59 | 'horiz-origin-x',
60 | 'image-rendering',
61 | 'letter-spacing',
62 | 'lighting-color',
63 | 'marker-end',
64 | 'marker-mid',
65 | 'marker-start',
66 | 'overline-position',
67 | 'overline-thickness',
68 | 'paint-order',
69 | 'panose-1',
70 | 'pointer-events',
71 | 'rendering-intent',
72 | 'shape-rendering',
73 | 'stop-color',
74 | 'stop-opacity',
75 | 'strikethrough-position',
76 | 'strikethrough-thickness',
77 | 'stroke-dasharray',
78 | 'stroke-dashoffset',
79 | 'stroke-linecap',
80 | 'stroke-linejoin',
81 | 'stroke-miterlimit',
82 | 'stroke-opacity',
83 | 'stroke-width',
84 | 'text-anchor',
85 | 'text-decoration',
86 | 'text-rendering',
87 | 'underline-position',
88 | 'underline-thickness',
89 | 'unicode-bidi',
90 | 'unicode-range',
91 | 'units-per-em',
92 | 'v-alphabetic',
93 | 'v-hanging',
94 | 'v-ideographic',
95 | 'v-mathematical',
96 | 'vector-effect',
97 | 'vert-adv-y',
98 | 'vert-origin-x',
99 | 'vert-origin-y',
100 | 'word-spacing',
101 | 'writing-mode',
102 | 'x-height',
103 | 'xlink:actuate',
104 | 'xlink:arcrole',
105 | 'xlink:href',
106 | 'xlink:role',
107 | 'xlink:show',
108 | 'xlink:title',
109 | 'xlink:type',
110 | 'xml:base',
111 | 'xmlns:xlink',
112 | 'xml:lang',
113 | 'xml:space',
114 | ];
115 |
116 | var SVGDOMPropertyConfig = {
117 | Properties: {
118 | autoReverse: HAS_STRING_BOOLEAN_VALUE,
119 | externalResourcesRequired: HAS_STRING_BOOLEAN_VALUE,
120 | preserveAlpha: HAS_STRING_BOOLEAN_VALUE,
121 | },
122 | DOMAttributeNames: {
123 | autoReverse: 'autoReverse',
124 | externalResourcesRequired: 'externalResourcesRequired',
125 | preserveAlpha: 'preserveAlpha',
126 | },
127 | DOMAttributeNamespaces: {
128 | xlinkActuate: NS.xlink,
129 | xlinkArcrole: NS.xlink,
130 | xlinkHref: NS.xlink,
131 | xlinkRole: NS.xlink,
132 | xlinkShow: NS.xlink,
133 | xlinkTitle: NS.xlink,
134 | xlinkType: NS.xlink,
135 | xmlBase: NS.xml,
136 | xmlLang: NS.xml,
137 | xmlSpace: NS.xml,
138 | },
139 | };
140 |
141 | var CAMELIZE = /[\-\:]([a-z])/g;
142 | var capitalize = token => token[1].toUpperCase();
143 |
144 | ATTRS.forEach(original => {
145 | var reactName = original.replace(CAMELIZE, capitalize);
146 |
147 | SVGDOMPropertyConfig.Properties[reactName] = 0;
148 | SVGDOMPropertyConfig.DOMAttributeNames[reactName] = original;
149 | });
150 |
151 | export default SVGDOMPropertyConfig;
152 |
--------------------------------------------------------------------------------
/src/shared/ReactDOMInvalidARIAHook.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import warning from 'fbjs/lib/warning';
9 | import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
10 |
11 | import {ATTRIBUTE_NAME_CHAR} from './DOMProperty';
12 | import isCustomComponent from './isCustomComponent';
13 | import validAriaProperties from './validAriaProperties';
14 |
15 | var warnedProperties = {};
16 | var rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$');
17 | var rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$');
18 |
19 | var hasOwnProperty = Object.prototype.hasOwnProperty;
20 |
21 | function getStackAddendum() {
22 | var stack = ReactDebugCurrentFrame.getStackAddendum();
23 | return stack != null ? stack : '';
24 | }
25 |
26 | function validateProperty(tagName, name) {
27 | if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) {
28 | return true;
29 | }
30 |
31 | if (rARIACamel.test(name)) {
32 | var ariaName = 'aria-' + name.slice(4).toLowerCase();
33 | var correctName = validAriaProperties.hasOwnProperty(ariaName)
34 | ? ariaName
35 | : null;
36 |
37 | // If this is an aria-* attribute, but is not listed in the known DOM
38 | // DOM properties, then it is an invalid aria-* attribute.
39 | if (correctName == null) {
40 | warning(
41 | false,
42 | 'Invalid ARIA attribute `%s`. ARIA attributes follow the pattern aria-* and must be lowercase.%s',
43 | name,
44 | getStackAddendum(),
45 | );
46 | warnedProperties[name] = true;
47 | return true;
48 | }
49 | // aria-* attributes should be lowercase; suggest the lowercase version.
50 | if (name !== correctName) {
51 | warning(
52 | false,
53 | 'Invalid ARIA attribute `%s`. Did you mean `%s`?%s',
54 | name,
55 | correctName,
56 | getStackAddendum(),
57 | );
58 | warnedProperties[name] = true;
59 | return true;
60 | }
61 | }
62 |
63 | if (rARIA.test(name)) {
64 | var lowerCasedName = name.toLowerCase();
65 | var standardName = validAriaProperties.hasOwnProperty(lowerCasedName)
66 | ? lowerCasedName
67 | : null;
68 |
69 | // If this is an aria-* attribute, but is not listed in the known DOM
70 | // DOM properties, then it is an invalid aria-* attribute.
71 | if (standardName == null) {
72 | warnedProperties[name] = true;
73 | return false;
74 | }
75 | // aria-* attributes should be lowercase; suggest the lowercase version.
76 | if (name !== standardName) {
77 | warning(
78 | false,
79 | 'Unknown ARIA attribute `%s`. Did you mean `%s`?%s',
80 | name,
81 | standardName,
82 | getStackAddendum(),
83 | );
84 | warnedProperties[name] = true;
85 | return true;
86 | }
87 | }
88 |
89 | return true;
90 | }
91 |
92 | function warnInvalidARIAProps(type, props) {
93 | const invalidProps = [];
94 |
95 | for (var key in props) {
96 | var isValid = validateProperty(type, key);
97 | if (!isValid) {
98 | invalidProps.push(key);
99 | }
100 | }
101 |
102 | const unknownPropString = invalidProps
103 | .map(prop => '`' + prop + '`')
104 | .join(', ');
105 |
106 | if (invalidProps.length === 1) {
107 | warning(
108 | false,
109 | 'Invalid aria prop %s on <%s> tag. ' +
110 | 'For details, see https://fb.me/invalid-aria-prop%s',
111 | unknownPropString,
112 | type,
113 | getStackAddendum(),
114 | );
115 | } else if (invalidProps.length > 1) {
116 | warning(
117 | false,
118 | 'Invalid aria props %s on <%s> tag. ' +
119 | 'For details, see https://fb.me/invalid-aria-prop%s',
120 | unknownPropString,
121 | type,
122 | getStackAddendum(),
123 | );
124 | }
125 | }
126 |
127 | export function validateProperties(type, props) {
128 | if (isCustomComponent(type, props)) {
129 | return;
130 | }
131 | warnInvalidARIAProps(type, props);
132 | }
133 |
--------------------------------------------------------------------------------
/src/shared/HTMLDOMPropertyConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {injection} from './DOMProperty';
9 |
10 | var MUST_USE_PROPERTY = injection.MUST_USE_PROPERTY;
11 | var HAS_BOOLEAN_VALUE = injection.HAS_BOOLEAN_VALUE;
12 | var HAS_NUMERIC_VALUE = injection.HAS_NUMERIC_VALUE;
13 | var HAS_POSITIVE_NUMERIC_VALUE = injection.HAS_POSITIVE_NUMERIC_VALUE;
14 | var HAS_OVERLOADED_BOOLEAN_VALUE = injection.HAS_OVERLOADED_BOOLEAN_VALUE;
15 | var HAS_STRING_BOOLEAN_VALUE = injection.HAS_STRING_BOOLEAN_VALUE;
16 |
17 | var HTMLDOMPropertyConfig = {
18 | // When adding attributes to this list, be sure to also add them to
19 | // the `possibleStandardNames` module to ensure casing and incorrect
20 | // name warnings.
21 | Properties: {
22 | allowFullScreen: HAS_BOOLEAN_VALUE,
23 | // specifies target context for links with `preload` type
24 | async: HAS_BOOLEAN_VALUE,
25 | // Note: there is a special case that prevents it from being written to the DOM
26 | // on the client side because the browsers are inconsistent. Instead we call focus().
27 | autoFocus: HAS_BOOLEAN_VALUE,
28 | autoPlay: HAS_BOOLEAN_VALUE,
29 | capture: HAS_OVERLOADED_BOOLEAN_VALUE,
30 | checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
31 | cols: HAS_POSITIVE_NUMERIC_VALUE,
32 | contentEditable: HAS_STRING_BOOLEAN_VALUE,
33 | controls: HAS_BOOLEAN_VALUE,
34 | default: HAS_BOOLEAN_VALUE,
35 | defer: HAS_BOOLEAN_VALUE,
36 | disabled: HAS_BOOLEAN_VALUE,
37 | download: HAS_OVERLOADED_BOOLEAN_VALUE,
38 | draggable: HAS_STRING_BOOLEAN_VALUE,
39 | formNoValidate: HAS_BOOLEAN_VALUE,
40 | hidden: HAS_BOOLEAN_VALUE,
41 | loop: HAS_BOOLEAN_VALUE,
42 | // Caution; `option.selected` is not updated if `select.multiple` is
43 | // disabled with `removeAttribute`.
44 | multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
45 | muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
46 | noValidate: HAS_BOOLEAN_VALUE,
47 | open: HAS_BOOLEAN_VALUE,
48 | playsInline: HAS_BOOLEAN_VALUE,
49 | readOnly: HAS_BOOLEAN_VALUE,
50 | required: HAS_BOOLEAN_VALUE,
51 | reversed: HAS_BOOLEAN_VALUE,
52 | rows: HAS_POSITIVE_NUMERIC_VALUE,
53 | rowSpan: HAS_NUMERIC_VALUE,
54 | scoped: HAS_BOOLEAN_VALUE,
55 | seamless: HAS_BOOLEAN_VALUE,
56 | selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
57 | size: HAS_POSITIVE_NUMERIC_VALUE,
58 | start: HAS_NUMERIC_VALUE,
59 | // support for projecting regular DOM Elements via V1 named slots ( shadow dom )
60 | span: HAS_POSITIVE_NUMERIC_VALUE,
61 | spellCheck: HAS_STRING_BOOLEAN_VALUE,
62 | // Style must be explicitly set in the attribute list. React components
63 | // expect a style object
64 | style: 0,
65 | // Keep it in the whitelist because it is case-sensitive for SVG.
66 | tabIndex: 0,
67 | // itemScope is for for Microdata support.
68 | // See http://schema.org/docs/gs.html
69 | itemScope: HAS_BOOLEAN_VALUE,
70 | // These attributes must stay in the white-list because they have
71 | // different attribute names (see DOMAttributeNames below)
72 | acceptCharset: 0,
73 | className: 0,
74 | htmlFor: 0,
75 | httpEquiv: 0,
76 | // Attributes with mutation methods must be specified in the whitelist
77 | // Set the string boolean flag to allow the behavior
78 | value: HAS_STRING_BOOLEAN_VALUE,
79 | },
80 | DOMAttributeNames: {
81 | acceptCharset: 'accept-charset',
82 | className: 'class',
83 | htmlFor: 'for',
84 | httpEquiv: 'http-equiv',
85 | },
86 | DOMMutationMethods: {
87 | value: function(node, value) {
88 | if (value == null) {
89 | return node.removeAttribute('value');
90 | }
91 |
92 | // Number inputs get special treatment due to some edge cases in
93 | // Chrome. Let everything else assign the value attribute as normal.
94 | // https://github.com/facebook/react/issues/7253#issuecomment-236074326
95 | if (node.type !== 'number' || node.hasAttribute('value') === false) {
96 | node.setAttribute('value', '' + value);
97 | } else if (
98 | node.validity &&
99 | !node.validity.badInput &&
100 | node.ownerDocument.activeElement !== node
101 | ) {
102 | // Don't assign an attribute if validation reports bad
103 | // input. Chrome will clear the value. Additionally, don't
104 | // operate on inputs that have focus, otherwise Chrome might
105 | // strip off trailing decimal places and cause the user's
106 | // cursor position to jump to the beginning of the input.
107 | //
108 | // In ReactDOMInput, we have an onBlur event that will trigger
109 | // this function again when focus is lost.
110 | node.setAttribute('value', '' + value);
111 | }
112 | },
113 | },
114 | };
115 |
116 | export default HTMLDOMPropertyConfig;
117 |
--------------------------------------------------------------------------------
/src/server/ReactDOMNodeStreamRenderer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import {Readable} from 'stream';
9 | import { Transform } from 'stream';
10 |
11 | import ReactPartialRenderer from './ReactPartialRenderer';
12 |
13 | // This is a Readable Node.js stream which wraps the ReactDOMPartialRenderer.
14 | class ReactMarkupReadableStream extends Readable {
15 | constructor(element, makeStaticMarkup, cache, streamingStart, memLife) {
16 | // Calls the stream.Readable(options) constructor. Consider exposing built-in
17 | // features like highWaterMark in the future.
18 | super({});
19 | this.cache = cache;
20 | this.streamingStart = streamingStart;
21 | this.memLife = memLife;
22 | this.partialRenderer = new ReactPartialRenderer(element, makeStaticMarkup);
23 | }
24 |
25 | async _read(size) {
26 | try {
27 | let readOutput = await this.partialRenderer.read(size,
28 | this.cache,
29 | true,
30 | this.streamingStart,
31 | this.memLife);
32 | this.push(readOutput);
33 | } catch (err) {
34 | this.emit('error', err);
35 | }
36 | }
37 | }
38 |
39 | function createCacheStream(cache, streamingStart, memLife = 0) {
40 | const bufferedChunks = [];
41 | return new Transform({
42 | // transform() is called with each chunk of data
43 | transform(data, enc, cb) {
44 | // We store the chunk of data (which is a Buffer) in memory
45 | bufferedChunks.push(data);
46 | // Then pass the data unchanged onwards to the next stream
47 | cb(null, data);
48 | },
49 |
50 | // flush() is called when everything is done
51 | flush(cb) {
52 | // We concatenate all the buffered chunks of HTML to get the full HTML, then cache it at "key"
53 | let html = bufferedChunks.join("");
54 | delete streamingStart.sliceStartCount;
55 |
56 | for (let component in streamingStart) {
57 | let tagStack = [];
58 | let tagStart;
59 | let tagEnd;
60 |
61 | do {
62 | if (!tagStart) {
63 | tagStart = streamingStart[component];
64 | } else {
65 | tagStart = (html[tagEnd] === '<') ? tagEnd : html.indexOf('<', tagEnd);
66 | }
67 | tagEnd = html.indexOf('>', tagStart) + 1;
68 | // Skip stack logic for void/self-closing elements and HTML comments
69 | if (html[tagEnd - 2] !== '/' && html[tagStart + 1] !== '!') {
70 | // Push opening tags onto stack; pop closing tags off of stack
71 | if (html[tagStart + 1] !== '/') {
72 | tagStack.push(html.slice(tagStart, tagEnd));
73 | } else {
74 | tagStack.pop();
75 | }
76 | }
77 | } while (tagStack.length !== 0);
78 | // cache component by slicing 'html'
79 | if (memLife) {
80 | cache.set(component, html.slice(streamingStart[component], tagEnd), memLife, (err) => {
81 | if (err) {
82 | console.log(err);
83 | }
84 | });
85 | } else {
86 | cache.set(component, html.slice(streamingStart[component], tagEnd));
87 | }
88 | }
89 | cb();
90 | },
91 | });
92 | }
93 | /**
94 | * Render a ReactElement to its initial HTML. This should only be used on the
95 | * server.
96 | * See https://reactjs.org/docs/react-dom-stream.html#rendertonodestream
97 | */
98 | function originalRenderToNodeStream(element, cache, streamingStart, memLife=0) {
99 | return new ReactMarkupReadableStream(element, false, cache, streamingStart, memLife);
100 | }
101 |
102 | export function renderToNodeStream(element, cache, res, htmlStart, htmlEnd, memLife) {
103 |
104 | const streamingStart = {
105 | sliceStartCount: htmlStart.length,
106 | };
107 |
108 | const cacheStream = createCacheStream(cache, streamingStart);
109 | cacheStream.pipe(res);
110 | cacheStream.write(htmlStart);
111 |
112 | const stream = originalRenderToNodeStream(element, cache, streamingStart, memLife);
113 | stream.pipe(cacheStream, { end: false });
114 | stream.on("end", () => {
115 | cacheStream.end(htmlEnd);
116 | });
117 |
118 | }
119 |
120 | /**
121 | * Similar to renderToNodeStream, except this doesn't create extra DOM attributes
122 | * such as data-react-id that React uses internally.
123 | * See https://reactjs.org/docs/react-dom-stream.html#rendertostaticnodestream
124 | */
125 | function originalRenderToStaticNodeStream(element, cache, streamingStart, memLife=0) {
126 | return new ReactMarkupReadableStream(element, true, cache, streamingStart, memLife);
127 | }
128 |
129 | export function renderToStaticNodeStream(element, cache, res, htmlStart, htmlEnd, memLife) {
130 |
131 | const streamingStart = {
132 | sliceStartCount: htmlStart.length,
133 | };
134 |
135 | const cacheStream = createCacheStream(cache, streamingStart);
136 | cacheStream.pipe(res);
137 | cacheStream.write(htmlStart);
138 |
139 | const stream = originalRenderToStaticNodeStream(element, cache, streamingStart, memLife);
140 | stream.pipe(cacheStream, { end: false });
141 | stream.on("end", () => {
142 | cacheStream.end(htmlEnd);
143 | });
144 | }
145 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Component Caching
2 |
3 | ## Overview
4 | React Component Caching is a component-level caching library for faster server-side rendering with React 16.
5 | - Use any of React's four server-side rendering methods. Rendering is **asynchronous**.
6 | - Cache components using a simple or template strategy.
7 | - Choose from three cache implementations (LRU, Redis, or Memcached).
8 |
9 | ## Installation
10 | Using npm:
11 | ```shell
12 | $ npm install react-component-caching
13 | ```
14 |
15 | ## Usage
16 | ### In Node rendering server:
17 | Instantiate a cache and pass it to any rendering method (`renderToString`, `renderToStaticMarkup`, `renderToNodeStream`, or `renderToStaticNodeStream`) as a second argument. Wherever you would use `ReactDOM.renderToString`, use `ReactCC.renderToString`.
18 |
19 | **Note: All of these methods are asynchronous, and return a promise. To use them, `await` the response before rendering**
20 | ```javascript
21 | const ReactCC = require("react-component-caching");
22 | const cache = new ReactCC.ComponentCache();
23 |
24 | app.get('/example', async (req,res) => {
25 | const renderString = await ReactCC.renderToString(