mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Implement JSX whitespace rules
So far we've been preserving JSX whitespace all the way through to codegen. But JSX has clear rules around whitespace handling, which allows us to trim whitespace in the input in lots of cases. For the most part this doesn't change our output, but I think that’s generally because of prettier. This PR should make a big difference when debugging the compiler, by removing all the whitespace JsxText values. But in some edge-cases it really makes a difference in the output since we can avoid memo slots for strings like `"\n "`.. ## Test Plan * Experimented with our internal tool to verify transform output to confirm that JSXText whitespace does not impact fbt transform results. * Synced and tested profile page, looks fine
This commit is contained in:
@@ -2829,9 +2829,13 @@ function lowerJsxElement(
|
||||
return lowerExpressionToTemporary(builder, expression);
|
||||
}
|
||||
} else if (exprPath.isJSXText()) {
|
||||
const text = trimJsxText(exprPath.node.value);
|
||||
if (text === null) {
|
||||
return null;
|
||||
}
|
||||
const place = lowerValueToTemporary(builder, {
|
||||
kind: "JSXText",
|
||||
value: exprPath.node.value,
|
||||
value: text,
|
||||
loc: exprLoc,
|
||||
});
|
||||
return place;
|
||||
@@ -2851,6 +2855,67 @@ function lowerJsxElement(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims whitespace according to the JSX spec:
|
||||
* > JSX removes whitespace at the beginning and ending of a line.
|
||||
* > It also removes blank lines. New lines adjacent to tags are removed;
|
||||
* > new lines that occur in the middle of string literals are condensed
|
||||
* > into a single space.
|
||||
*
|
||||
* From https://legacy.reactjs.org/docs/jsx-in-depth.html#string-literals-1
|
||||
*
|
||||
* Implementation adapted from Babel:
|
||||
* https://github.com/babel/babel/blob/54d30f206057be64b496d2da1ec8c49d244ba4e4/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts#L5
|
||||
*/
|
||||
function trimJsxText(original: string): string | null {
|
||||
const lines = original.split(/\r\n|\n|\r/);
|
||||
|
||||
let lastNonEmptyLine = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].match(/[^ \t]/)) {
|
||||
lastNonEmptyLine = i;
|
||||
}
|
||||
}
|
||||
|
||||
let str = "";
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
const isFirstLine = i === 0;
|
||||
const isLastLine = i === lines.length - 1;
|
||||
const isLastNonEmptyLine = i === lastNonEmptyLine;
|
||||
|
||||
// replace rendered whitespace tabs with spaces
|
||||
let trimmedLine = line.replace(/\t/g, " ");
|
||||
|
||||
// trim whitespace touching a newline
|
||||
if (!isFirstLine) {
|
||||
trimmedLine = trimmedLine.replace(/^[ ]+/, "");
|
||||
}
|
||||
|
||||
// trim whitespace touching an endline
|
||||
if (!isLastLine) {
|
||||
trimmedLine = trimmedLine.replace(/[ ]+$/, "");
|
||||
}
|
||||
|
||||
if (trimmedLine) {
|
||||
if (!isLastNonEmptyLine) {
|
||||
trimmedLine += " ";
|
||||
}
|
||||
|
||||
str += trimmedLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (str.length !== 0) {
|
||||
return str;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function lowerFunctionExpression(
|
||||
builder: HIRBuilder,
|
||||
expr: NodePath<t.FunctionExpression | t.ArrowFunctionExpression>
|
||||
|
||||
+2
-2
@@ -28,9 +28,9 @@ function Component({ name, data, icon }) {
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
import fbt from "fbt";
|
||||
|
||||
function Component(t39) {
|
||||
function Component(t29) {
|
||||
const $ = useMemoCache(4);
|
||||
const { name, data, icon } = t39;
|
||||
const { name, data, icon } = t29;
|
||||
const c_0 = $[0] !== name;
|
||||
const c_1 = $[1] !== icon;
|
||||
const c_2 = $[2] !== data;
|
||||
|
||||
+38
-63
@@ -24,101 +24,76 @@ function Component(props) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(21);
|
||||
const $ = useMemoCache(15);
|
||||
const item = useFragment(FRAGMENT, props.item);
|
||||
useFreeze(item);
|
||||
const c_0 = $[0] !== item;
|
||||
let t1;
|
||||
let T2;
|
||||
let t3;
|
||||
let t0;
|
||||
let t4;
|
||||
let T5;
|
||||
let t6;
|
||||
let T3;
|
||||
if (c_0) {
|
||||
const count = new MaybeMutable(item);
|
||||
|
||||
T5 = View;
|
||||
t6 = "\n ";
|
||||
T3 = View;
|
||||
T2 = View;
|
||||
t3 = "\n ";
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <span>Text</span>;
|
||||
$[8] = t0;
|
||||
$[5] = t0;
|
||||
} else {
|
||||
t0 = $[8];
|
||||
t0 = $[5];
|
||||
}
|
||||
t4 = "\n ";
|
||||
t1 = maybeMutate(count);
|
||||
$[0] = item;
|
||||
$[1] = t1;
|
||||
$[2] = T2;
|
||||
$[3] = t3;
|
||||
$[4] = t0;
|
||||
$[5] = t4;
|
||||
$[6] = T5;
|
||||
$[7] = t6;
|
||||
$[3] = t0;
|
||||
$[4] = T3;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
T2 = $[2];
|
||||
t3 = $[3];
|
||||
t0 = $[4];
|
||||
t4 = $[5];
|
||||
T5 = $[6];
|
||||
t6 = $[7];
|
||||
t0 = $[3];
|
||||
T3 = $[4];
|
||||
}
|
||||
const c_9 = $[9] !== t1;
|
||||
let t7;
|
||||
if (c_9) {
|
||||
t7 = <span>{t1}</span>;
|
||||
$[9] = t1;
|
||||
$[10] = t7;
|
||||
const c_6 = $[6] !== t1;
|
||||
let t4;
|
||||
if (c_6) {
|
||||
t4 = <span>{t1}</span>;
|
||||
$[6] = t1;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t7 = $[10];
|
||||
t4 = $[7];
|
||||
}
|
||||
const c_11 = $[11] !== T2;
|
||||
const c_12 = $[12] !== t3;
|
||||
const c_13 = $[13] !== t0;
|
||||
const c_14 = $[14] !== t4;
|
||||
const c_15 = $[15] !== t7;
|
||||
let t8;
|
||||
if (c_11 || c_12 || c_13 || c_14 || c_15) {
|
||||
t8 = (
|
||||
const c_8 = $[8] !== T2;
|
||||
const c_9 = $[9] !== t0;
|
||||
const c_10 = $[10] !== t4;
|
||||
let t5;
|
||||
if (c_8 || c_9 || c_10) {
|
||||
t5 = (
|
||||
<T2>
|
||||
{t3}
|
||||
{t0}
|
||||
{t4}
|
||||
{t7}
|
||||
</T2>
|
||||
);
|
||||
$[11] = T2;
|
||||
$[12] = t3;
|
||||
$[13] = t0;
|
||||
$[14] = t4;
|
||||
$[15] = t7;
|
||||
$[16] = t8;
|
||||
$[8] = T2;
|
||||
$[9] = t0;
|
||||
$[10] = t4;
|
||||
$[11] = t5;
|
||||
} else {
|
||||
t8 = $[16];
|
||||
t5 = $[11];
|
||||
}
|
||||
const c_17 = $[17] !== T5;
|
||||
const c_18 = $[18] !== t6;
|
||||
const c_19 = $[19] !== t8;
|
||||
let t9;
|
||||
if (c_17 || c_18 || c_19) {
|
||||
t9 = (
|
||||
<T5>
|
||||
{t6}
|
||||
{t8}
|
||||
</T5>
|
||||
);
|
||||
$[17] = T5;
|
||||
$[18] = t6;
|
||||
$[19] = t8;
|
||||
$[20] = t9;
|
||||
const c_12 = $[12] !== T3;
|
||||
const c_13 = $[13] !== t5;
|
||||
let t6;
|
||||
if (c_12 || c_13) {
|
||||
t6 = <T3>{t5}</T3>;
|
||||
$[12] = T3;
|
||||
$[13] = t5;
|
||||
$[14] = t6;
|
||||
} else {
|
||||
t9 = $[20];
|
||||
t6 = $[14];
|
||||
}
|
||||
return t9;
|
||||
return t6;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+18
-33
@@ -21,63 +21,48 @@ function Component(props) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(9);
|
||||
const $ = useMemoCache(6);
|
||||
let t1;
|
||||
let T2;
|
||||
let t3;
|
||||
let t0;
|
||||
let t4;
|
||||
let T5;
|
||||
let t6;
|
||||
let T3;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const count = new MaybeMutable();
|
||||
|
||||
T5 = View;
|
||||
t6 = "\n ";
|
||||
T3 = View;
|
||||
T2 = View;
|
||||
t3 = "\n ";
|
||||
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <span>Text</span>;
|
||||
$[7] = t0;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[7];
|
||||
t0 = $[4];
|
||||
}
|
||||
t4 = "\n ";
|
||||
t1 = maybeMutate(count);
|
||||
$[0] = t1;
|
||||
$[1] = T2;
|
||||
$[2] = t3;
|
||||
$[3] = t0;
|
||||
$[4] = t4;
|
||||
$[5] = T5;
|
||||
$[6] = t6;
|
||||
$[2] = t0;
|
||||
$[3] = T3;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
T2 = $[1];
|
||||
t3 = $[2];
|
||||
t0 = $[3];
|
||||
t4 = $[4];
|
||||
T5 = $[5];
|
||||
t6 = $[6];
|
||||
t0 = $[2];
|
||||
T3 = $[3];
|
||||
}
|
||||
let t7;
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t7 = (
|
||||
<T5>
|
||||
{t6}
|
||||
let t4;
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = (
|
||||
<T3>
|
||||
<T2>
|
||||
{t3}
|
||||
{t0}
|
||||
{t4}
|
||||
<span>{t1}</span>
|
||||
</T2>
|
||||
</T5>
|
||||
</T3>
|
||||
);
|
||||
$[8] = t7;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t7 = $[8];
|
||||
t4 = $[5];
|
||||
}
|
||||
return t7;
|
||||
return t4;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+2
-2
@@ -43,9 +43,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(t29) {
|
||||
function Component(t27) {
|
||||
const $ = useMemoCache(2);
|
||||
const { items } = t29;
|
||||
const { items } = t27;
|
||||
const c_0 = $[0] !== items;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
|
||||
+1
@@ -78,6 +78,7 @@ function Component() {
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 42 }],
|
||||
|
||||
+3
-3
@@ -27,9 +27,9 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
import { unstable_useMemoCache as useMemoCache } from "react"; // @enableMergeConsecutiveScopes
|
||||
function Component(id) {
|
||||
const $ = useMemoCache(3);
|
||||
let t25;
|
||||
t25 = undefined;
|
||||
const bar = t25;
|
||||
let t22;
|
||||
t22 = undefined;
|
||||
const bar = t22;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <Bar title={bar} />;
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ function Component(props) {
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = (
|
||||
<div>
|
||||
rendering took {time} at {now}
|
||||
rendering took
|
||||
{time} at {now}
|
||||
</div>
|
||||
);
|
||||
$[1] = t1;
|
||||
|
||||
Reference in New Issue
Block a user