Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c4b320aa |
221
.github/workflows/scripts/ghstack/check_permissions.js
vendored
Normal file
221
.github/workflows/scripts/ghstack/check_permissions.js
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env node
|
||||
// JS rewrite of https://github.com/Chillee/ghstack_land_example/blob/main/.github/workflows/scripts/ghstack-perm-check.py
|
||||
'use strict';
|
||||
|
||||
const {spawnSync} = require('child_process');
|
||||
const process = require('process');
|
||||
const {Octokit} = require('@octokit/rest');
|
||||
|
||||
const OWNER = 'facebook';
|
||||
const REPO = 'react';
|
||||
|
||||
async function must(cond, msg, octokit, issue_number) {
|
||||
if (!cond) {
|
||||
console.error(msg);
|
||||
try {
|
||||
await octokit.issues.createComment({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
issue_number,
|
||||
body: `ghstack bot failed: ${msg}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to post comment:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
if (!GITHUB_TOKEN) {
|
||||
console.error('GITHUB_TOKEN environment variable is not set.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const octokit = new Octokit({auth: GITHUB_TOKEN});
|
||||
const prNumber = parseInt(process.argv[2]);
|
||||
const headRef = process.argv[3];
|
||||
|
||||
console.log(headRef);
|
||||
await must(
|
||||
headRef && /^gh\/[A-Za-z0-9-]+\/[0-9]+\/head$/.test(headRef),
|
||||
'Not a ghstack PR',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
const origRef = headRef.replace('/head', '/orig');
|
||||
|
||||
console.log(':: Fetching newest main...');
|
||||
let result = spawnSync('git', ['fetch', 'origin', 'main'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
await must(
|
||||
result.status === 0,
|
||||
"Can't fetch main",
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
console.log(':: Fetching orig branch...');
|
||||
result = spawnSync('git', ['fetch', 'origin', origRef], {stdio: 'inherit'});
|
||||
await must(
|
||||
result.status === 0,
|
||||
"Can't fetch orig branch",
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
result = spawnSync(
|
||||
'git',
|
||||
['log', 'FETCH_HEAD...$(git merge-base FETCH_HEAD origin/main)'],
|
||||
{shell: true}
|
||||
);
|
||||
const out = result.stdout.toString();
|
||||
await must(
|
||||
result.status === 0,
|
||||
'`git log` command failed!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
const regex =
|
||||
/Pull Request resolved: https:\/\/github\.com\/.*?\/pull\/([0-9]+)/g;
|
||||
const prNumbers = [];
|
||||
let match;
|
||||
while ((match = regex.exec(out)) !== null) {
|
||||
prNumbers.push(parseInt(match[1], 10));
|
||||
}
|
||||
console.log(prNumbers);
|
||||
await must(
|
||||
prNumbers.length && prNumbers[0] === prNumber,
|
||||
'Extracted PR numbers not seems right!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
for (const n of prNumbers) {
|
||||
process.stdout.write(`:: Checking PR status #${n}... `);
|
||||
|
||||
let prObj;
|
||||
try {
|
||||
const {data} = await octokit.pulls.get({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
pull_number: n,
|
||||
});
|
||||
prObj = data;
|
||||
} catch (error) {
|
||||
await must(
|
||||
false,
|
||||
'Error Getting PR Object!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
|
||||
let reviews;
|
||||
try {
|
||||
const {data} = await octokit.request(
|
||||
'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
|
||||
{
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
pull_number: prNumber,
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
}
|
||||
);
|
||||
reviews = data;
|
||||
} catch (error) {
|
||||
await must(
|
||||
false,
|
||||
'Error Getting PR Reviews!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
|
||||
let approved = false;
|
||||
for (const review of reviews) {
|
||||
if (review.state === 'COMMENTED') continue;
|
||||
|
||||
await must(
|
||||
['APPROVED', 'DISMISSED'].includes(review.state),
|
||||
`@${review.user.login} has stamped PR #${n} \`${review.state}\`, please resolve it first!`,
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
if (review.state === 'APPROVED') {
|
||||
approved = true;
|
||||
}
|
||||
}
|
||||
await must(
|
||||
approved,
|
||||
`PR #${n} is not approved yet!`,
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
|
||||
let checkruns;
|
||||
try {
|
||||
const {data} = await octokit.checks.listForRef({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
ref: prObj.head.sha,
|
||||
});
|
||||
checkruns = data;
|
||||
} catch (error) {
|
||||
await must(
|
||||
false,
|
||||
'Error getting check runs status!',
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
|
||||
for (const cr of checkruns.check_runs) {
|
||||
const status = cr.conclusion ? cr.conclusion : cr.status;
|
||||
const name = cr.name;
|
||||
if (name === 'Copilot for PRs') continue;
|
||||
await must(
|
||||
['success', 'neutral'].includes(status),
|
||||
`PR #${n} check-run \`${name}\`'s status \`${status}\` is not success!`,
|
||||
octokit,
|
||||
OWNER,
|
||||
REPO,
|
||||
prNumber
|
||||
);
|
||||
}
|
||||
console.log('SUCCESS!');
|
||||
}
|
||||
|
||||
console.log(':: All PRs are ready to be landed!');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Unexpected error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
12
.github/workflows/scripts/ghstack/package.json
vendored
Normal file
12
.github/workflows/scripts/ghstack/package.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "ghstack-perm-check",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"check-permissions": "node ./check_permissions.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^21.1.1"
|
||||
}
|
||||
}
|
||||
112
.github/workflows/scripts/ghstack/yarn.lock
vendored
Normal file
112
.github/workflows/scripts/ghstack/yarn.lock
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@octokit/auth-token@^5.0.0":
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.2.tgz#68a486714d7a7fd1df56cb9bc89a860a0de866de"
|
||||
integrity sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==
|
||||
|
||||
"@octokit/core@^6.1.4":
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.4.tgz#f5ccf911cc95b1ce9daf6de425d1664392f867db"
|
||||
integrity sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==
|
||||
dependencies:
|
||||
"@octokit/auth-token" "^5.0.0"
|
||||
"@octokit/graphql" "^8.1.2"
|
||||
"@octokit/request" "^9.2.1"
|
||||
"@octokit/request-error" "^6.1.7"
|
||||
"@octokit/types" "^13.6.2"
|
||||
before-after-hook "^3.0.2"
|
||||
universal-user-agent "^7.0.0"
|
||||
|
||||
"@octokit/endpoint@^10.1.3":
|
||||
version "10.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.3.tgz#bfe8ff2ec213eb4216065e77654bfbba0fc6d4de"
|
||||
integrity sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.6.2"
|
||||
universal-user-agent "^7.0.2"
|
||||
|
||||
"@octokit/graphql@^8.1.2":
|
||||
version "8.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.2.1.tgz#0cb83600e6b4009805acc1c56ae8e07e6c991b78"
|
||||
integrity sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==
|
||||
dependencies:
|
||||
"@octokit/request" "^9.2.2"
|
||||
"@octokit/types" "^13.8.0"
|
||||
universal-user-agent "^7.0.0"
|
||||
|
||||
"@octokit/openapi-types@^24.2.0":
|
||||
version "24.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-24.2.0.tgz#3d55c32eac0d38da1a7083a9c3b0cca77924f7d3"
|
||||
integrity sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==
|
||||
|
||||
"@octokit/plugin-paginate-rest@^11.4.2":
|
||||
version "11.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz#e5e9ff3530e867c3837fdbff94ce15a2468a1f37"
|
||||
integrity sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.10.0"
|
||||
|
||||
"@octokit/plugin-request-log@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz#ccb75d9705de769b2aa82bcd105cc96eb0c00f69"
|
||||
integrity sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods@^13.3.0":
|
||||
version "13.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.5.0.tgz#d8c8ca2123b305596c959a9134dfa8b0495b0ba6"
|
||||
integrity sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.10.0"
|
||||
|
||||
"@octokit/request-error@^6.1.7":
|
||||
version "6.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.7.tgz#44fc598f5cdf4593e0e58b5155fe2e77230ff6da"
|
||||
integrity sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==
|
||||
dependencies:
|
||||
"@octokit/types" "^13.6.2"
|
||||
|
||||
"@octokit/request@^9.2.1", "@octokit/request@^9.2.2":
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.2.2.tgz#754452ec4692d7fdc32438a14e028eba0e6b2c09"
|
||||
integrity sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==
|
||||
dependencies:
|
||||
"@octokit/endpoint" "^10.1.3"
|
||||
"@octokit/request-error" "^6.1.7"
|
||||
"@octokit/types" "^13.6.2"
|
||||
fast-content-type-parse "^2.0.0"
|
||||
universal-user-agent "^7.0.2"
|
||||
|
||||
"@octokit/rest@^21.1.1":
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-21.1.1.tgz#7a70455ca451b1d253e5b706f35178ceefb74de2"
|
||||
integrity sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==
|
||||
dependencies:
|
||||
"@octokit/core" "^6.1.4"
|
||||
"@octokit/plugin-paginate-rest" "^11.4.2"
|
||||
"@octokit/plugin-request-log" "^5.3.1"
|
||||
"@octokit/plugin-rest-endpoint-methods" "^13.3.0"
|
||||
|
||||
"@octokit/types@^13.10.0", "@octokit/types@^13.6.2", "@octokit/types@^13.8.0":
|
||||
version "13.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.10.0.tgz#3e7c6b19c0236c270656e4ea666148c2b51fd1a3"
|
||||
integrity sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^24.2.0"
|
||||
|
||||
before-after-hook@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d"
|
||||
integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==
|
||||
|
||||
fast-content-type-parse@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz#c236124534ee2cb427c8d8e5ba35a4856947847b"
|
||||
integrity sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==
|
||||
|
||||
universal-user-agent@^7.0.0, universal-user-agent@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e"
|
||||
integrity sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==
|
||||
121
.github/workflows/shared_ghstack_land.yml
vendored
Normal file
121
.github/workflows/shared_ghstack_land.yml
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
name: (Shared) ghstack land
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
|
||||
jobs:
|
||||
check_access:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is_member_or_collaborator: ${{ steps.check_access.outputs.result }}
|
||||
steps:
|
||||
- name: Check access
|
||||
id: check_access
|
||||
if: ${{ github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR' }}
|
||||
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
check_maintainer:
|
||||
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' }}
|
||||
needs: [check_access]
|
||||
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
|
||||
permissions:
|
||||
# Used by check_maintainer
|
||||
contents: read
|
||||
with:
|
||||
actor: ${{ github.event.comment.user.login }}
|
||||
|
||||
ghstack_land:
|
||||
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/land') }}
|
||||
needs: [check_maintainer]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add reaction to comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const comment_id = "${{ github.event.comment.id }}"
|
||||
|
||||
await github.rest.reactions.createForCommitComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id,
|
||||
content: "rocket",
|
||||
});
|
||||
- name: Get PR details
|
||||
id: get-pr
|
||||
run: |
|
||||
PR_NUMBER=${{ github.event.issue.number }}
|
||||
echo "PR number is $PR_NUMBER"
|
||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get PR details using GitHub API
|
||||
PR_DATA=$(curl -s \
|
||||
-H "Authorization: token ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"${{ github.api_url }}/repos/${{ github.repository }}/pulls/$PR_NUMBER")
|
||||
|
||||
# Extract useful information
|
||||
PR_HEAD_REF=$(echo "$PR_DATA" | jq -r .head.ref)
|
||||
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r .head.sha)
|
||||
PR_URL="${{ github.server_url }}/${{ github.repository }}/pull/$PR_NUMBER"
|
||||
|
||||
echo "pr_branch=$PR_HEAD_REF" >> $GITHUB_OUTPUT
|
||||
echo "pr_sha=$PR_HEAD_SHA" >> $GITHUB_OUTPUT
|
||||
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
|
||||
echo "pr_branch=$PR_HEAD_REF"
|
||||
echo "pr_sha=$PR_HEAD_SHA"
|
||||
echo "pr_url=$PR_URL"
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ghstack-pip-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install ghstack
|
||||
run: pip install requests ghstack
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: ghstack-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('.github/workflows/scripts/ghstack/yarn.lock') }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: .github/workflows/scripts/ghstack/yarn.lock
|
||||
- run: yarn install --cwd .github/workflows/scripts/ghstack --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Check Current CI Status
|
||||
run: |
|
||||
echo ${{ github.event.issue.number }}
|
||||
yarn --cwd .github/workflows/scripts/ghstack check-permissions ${{ github.event.issue.number }} ${{steps.get-pr.outputs.pr_branch}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Land It!
|
||||
run: |
|
||||
git config --global user.email "facebook-github-bot@users.noreply.github.com"
|
||||
git config --global user.name "Facebook Community Bot"
|
||||
cat <<EOF > ~/.ghstackrc
|
||||
[ghstack]
|
||||
github_url = github.com
|
||||
github_oauth = $GITHUB_TOKEN
|
||||
github_username = facebook-github-bot
|
||||
remote_name = origin
|
||||
EOF
|
||||
ghstack land "${{ steps.get-pr.outputs.pr_url }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
@@ -1,59 +0,0 @@
|
||||
## 19.1.0-rc.1 (April 21, 2025)
|
||||
|
||||
## eslint-plugin-react-hooks
|
||||
* Temporarily disable ref access in render validation [#32839](https://github.com/facebook/react/pull/32839) by [@poteto](https://github.com/poteto)
|
||||
* Fix type error with recommended config [#32666](https://github.com/facebook/react/pull/32666) by [@niklasholm](https://github.com/niklasholm)
|
||||
* Merge rule from eslint-plugin-react-compiler into `react-hooks` plugin [#32416](https://github.com/facebook/react/pull/32416) by [@michaelfaith](https://github.com/michaelfaith)
|
||||
* Add dev dependencies for typescript migration [#32279](https://github.com/facebook/react/pull/32279) by [@michaelfaith](https://github.com/michaelfaith)
|
||||
* Support v9 context api [#32045](https://github.com/facebook/react/pull/32045) by [@michaelfaith](https://github.com/michaelfaith)
|
||||
* Support eslint 8+ flat plugin syntax out of the box for eslint-plugin-react-compiler [#32120](https://github.com/facebook/react/pull/32120) by [@orta](https://github.com/orta)
|
||||
|
||||
## babel-plugin-react-compiler
|
||||
* Support satisfies operator [#32742](https://github.com/facebook/react/pull/32742) by [@rodrigofariow](https://github.com/rodrigofariow)
|
||||
* Fix inferEffectDependencies lint false positives [#32769](https://github.com/facebook/react/pull/32769) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Fix hoisting of let declarations [#32724](https://github.com/facebook/react/pull/32724) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Avoid failing builds when import specifiers conflict or shadow vars [#32663](https://github.com/facebook/react/pull/32663) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Optimize components declared with arrow function and implicit return and `compilationMode: 'infer'` [#31792](https://github.com/facebook/react/pull/31792) by [@dimaMachina](https://github.com/dimaMachina)
|
||||
* Validate static components [#32683](https://github.com/facebook/react/pull/32683) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Hoist dependencies from functions more conservatively [#32616](https://github.com/facebook/react/pull/32616) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Implement NumericLiteral as ObjectPropertyKey [#31791](https://github.com/facebook/react/pull/31791) by [@dimaMachina](https://github.com/dimaMachina)
|
||||
* Avoid bailouts when inserting gating [#32598](https://github.com/facebook/react/pull/32598) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Stop bailing out early for hoisted gated functions [#32597](https://github.com/facebook/react/pull/32597) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Add shape for Array.from [#32522](https://github.com/facebook/react/pull/32522) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Patch array and argument spread mutability [#32521](https://github.com/facebook/react/pull/32521) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Make CompilerError compatible with reflection [#32539](https://github.com/facebook/react/pull/32539) by [@poteto](https://github.com/poteto)
|
||||
* Add simple walltime measurement [#32331](https://github.com/facebook/react/pull/32331) by [@poteto](https://github.com/poteto)
|
||||
* Improve error messages for unhandled terminal and instruction kinds [#32324](https://github.com/facebook/react/pull/32324) by [@inottn](https://github.com/inottn)
|
||||
* Handle TSInstantiationExpression in lowerExpression [#32302](https://github.com/facebook/react/pull/32302) by [@inottn](https://github.com/inottn)
|
||||
* Fix invalid Array.map type [#32095](https://github.com/facebook/react/pull/32095) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Patch for JSX escape sequences in @babel/generator [#32131](https://github.com/facebook/react/pull/32131) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* `JSXText` emits incorrect with bracket [#32138](https://github.com/facebook/react/pull/32138) by [@himself65](https://github.com/himself65)
|
||||
* Validation against calling impure functions [#31960](https://github.com/facebook/react/pull/31960) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Always target node [#32091](https://github.com/facebook/react/pull/32091) by [@poteto](https://github.com/poteto)
|
||||
* Patch compilationMode:infer object method edge case [#32055](https://github.com/facebook/react/pull/32055) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Generate ts defs [#31994](https://github.com/facebook/react/pull/31994) by [@poteto](https://github.com/poteto)
|
||||
* Relax react peer dep requirement [#31915](https://github.com/facebook/react/pull/31915) by [@poteto](https://github.com/poteto)
|
||||
* Allow type cast expressions with refs [#31871](https://github.com/facebook/react/pull/31871) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Add shape for global Object.keys [#31583](https://github.com/facebook/react/pull/31583) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Optimize method calls w props receiver [#31775](https://github.com/facebook/react/pull/31775) by [@josephsavona](https://github.com/josephsavona)
|
||||
* Fix dropped ref with spread props in InlineJsxTransform [#31726](https://github.com/facebook/react/pull/31726) by [@jackpope](https://github.com/jackpope)
|
||||
* Support for non-declatation for in/of iterators [#31710](https://github.com/facebook/react/pull/31710) by [@mvitousek](https://github.com/mvitousek)
|
||||
* Support for context variable loop iterators [#31709](https://github.com/facebook/react/pull/31709) by [@mvitousek](https://github.com/mvitousek)
|
||||
* Replace deprecated dependency in `eslint-plugin-react-compiler` [#31629](https://github.com/facebook/react/pull/31629) by [@rakleed](https://github.com/rakleed)
|
||||
* Support enableRefAsProp in jsx transform [#31558](https://github.com/facebook/react/pull/31558) by [@jackpope](https://github.com/jackpope)
|
||||
* Fix: ref.current now correctly reactive [#31521](https://github.com/facebook/react/pull/31521) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Outline JSX with non-jsx children [#31442](https://github.com/facebook/react/pull/31442) by [@gsathya](https://github.com/gsathya)
|
||||
* Outline jsx with duplicate attributes [#31441](https://github.com/facebook/react/pull/31441) by [@gsathya](https://github.com/gsathya)
|
||||
* Store original and new prop names [#31440](https://github.com/facebook/react/pull/31440) by [@gsathya](https://github.com/gsathya)
|
||||
* Stabilize compiler output: sort deps and decls by name [#31362](https://github.com/facebook/react/pull/31362) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Bugfix for hoistable deps for nested functions [#31345](https://github.com/facebook/react/pull/31345) by [@mofeiZ](https://github.com/mofeiZ)
|
||||
* Remove compiler runtime-compat fixture library [#31430](https://github.com/facebook/react/pull/31430) by [@poteto](https://github.com/poteto)
|
||||
* Wrap inline jsx transform codegen in conditional [#31267](https://github.com/facebook/react/pull/31267) by [@jackpope](https://github.com/jackpope)
|
||||
* Check if local identifier is a hook when resolving globals [#31384](https://github.com/facebook/react/pull/31384) by [@poteto](https://github.com/poteto)
|
||||
* Handle member expr as computed property [#31344](https://github.com/facebook/react/pull/31344) by [@gsathya](https://github.com/gsathya)
|
||||
* Fix to ref access check to ban ref?.current [#31360](https://github.com/facebook/react/pull/31360) by [@mvitousek](https://github.com/mvitousek)
|
||||
* InlineJSXTransform transforms jsx inside function expressions [#31282](https://github.com/facebook/react/pull/31282) by [@josephsavona](https://github.com/josephsavona)
|
||||
|
||||
## Other
|
||||
* Add shebang to banner [#32225](https://github.com/facebook/react/pull/32225) by [@Jeremy-Hibiki](https://github.com/Jeremy-Hibiki)
|
||||
* remove terser from react-compiler-runtime build [#31326](https://github.com/facebook/react/pull/31326) by [@henryqdineen](https://github.com/henryqdineen)
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
DependencyPathEntry,
|
||||
FunctionExpression,
|
||||
GeneratedSource,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
PropertyLiteral,
|
||||
ReactiveScopeDependency,
|
||||
ScopeId,
|
||||
TInstruction,
|
||||
} from './HIR';
|
||||
|
||||
const DEBUG_PRINT = false;
|
||||
@@ -129,33 +127,6 @@ export function collectHoistablePropertyLoads(
|
||||
});
|
||||
}
|
||||
|
||||
export function collectHoistablePropertyLoadsInInnerFn(
|
||||
fnInstr: TInstruction<FunctionExpression>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
|
||||
): ReadonlyMap<BlockId, BlockInfo> {
|
||||
const fn = fnInstr.value.loweredFunc.func;
|
||||
const initialContext: CollectHoistablePropertyLoadsContext = {
|
||||
temporaries,
|
||||
knownImmutableIdentifiers: new Set(),
|
||||
hoistableFromOptionals,
|
||||
registry: new PropertyPathRegistry(),
|
||||
nestedFnImmutableContext: null,
|
||||
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
|
||||
? new Set()
|
||||
: getAssumedInvokedFunctions(fn),
|
||||
};
|
||||
const nestedFnImmutableContext = new Set(
|
||||
fn.context
|
||||
.filter(place =>
|
||||
isImmutableAtInstr(place.identifier, fnInstr.id, initialContext),
|
||||
)
|
||||
.map(place => place.identifier.id),
|
||||
);
|
||||
initialContext.nestedFnImmutableContext = nestedFnImmutableContext;
|
||||
return collectHoistablePropertyLoadsImpl(fn, initialContext);
|
||||
}
|
||||
|
||||
type CollectHoistablePropertyLoadsContext = {
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
|
||||
knownImmutableIdentifiers: ReadonlySet<IdentifierId>;
|
||||
|
||||
@@ -116,7 +116,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function findTemporariesUsedOutsideDeclaringScope(
|
||||
function findTemporariesUsedOutsideDeclaringScope(
|
||||
fn: HIRFunction,
|
||||
): ReadonlySet<DeclarationId> {
|
||||
/*
|
||||
@@ -378,7 +378,7 @@ type Decl = {
|
||||
scope: Stack<ReactiveScope>;
|
||||
};
|
||||
|
||||
export class DependencyCollectionContext {
|
||||
class Context {
|
||||
#declarations: Map<DeclarationId, Decl> = new Map();
|
||||
#reassignments: Map<Identifier, Decl> = new Map();
|
||||
|
||||
@@ -645,10 +645,7 @@ enum HIRValue {
|
||||
Terminal,
|
||||
}
|
||||
|
||||
export function handleInstruction(
|
||||
instr: Instruction,
|
||||
context: DependencyCollectionContext,
|
||||
): void {
|
||||
function handleInstruction(instr: Instruction, context: Context): void {
|
||||
const {id, value, lvalue} = instr;
|
||||
context.declare(lvalue.identifier, {
|
||||
id,
|
||||
@@ -711,7 +708,7 @@ function collectDependencies(
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
|
||||
const context = new DependencyCollectionContext(
|
||||
const context = new Context(
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
processedInstrsInOptional,
|
||||
|
||||
@@ -22,30 +22,18 @@ import {
|
||||
ScopeId,
|
||||
ReactiveScopeDependency,
|
||||
Place,
|
||||
ReactiveScope,
|
||||
ReactiveScopeDependencies,
|
||||
Terminal,
|
||||
isUseRefType,
|
||||
isSetStateType,
|
||||
isFireFunctionType,
|
||||
makeScopeId,
|
||||
} from '../HIR';
|
||||
import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
|
||||
import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
|
||||
import {ReactiveScopeDependencyTreeHIR} from '../HIR/DeriveMinimalDependenciesHIR';
|
||||
import {DEFAULT_EXPORT} from '../HIR/Environment';
|
||||
import {
|
||||
createTemporaryPlace,
|
||||
fixScopeAndIdentifierRanges,
|
||||
markInstructionIds,
|
||||
} from '../HIR/HIRBuilder';
|
||||
import {
|
||||
collectTemporariesSidemap,
|
||||
DependencyCollectionContext,
|
||||
handleInstruction,
|
||||
} from '../HIR/PropagateScopeDependenciesHIR';
|
||||
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
|
||||
import {empty} from '../Utils/Stack';
|
||||
import {getOrInsertWith} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
@@ -74,7 +62,10 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
const autodepFnLoads = new Map<IdentifierId, number>();
|
||||
const autodepModuleLoads = new Map<IdentifierId, Map<string, number>>();
|
||||
|
||||
const scopeInfos = new Map<ScopeId, ReactiveScopeDependencies>();
|
||||
const scopeInfos = new Map<
|
||||
ScopeId,
|
||||
{pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
|
||||
>();
|
||||
|
||||
const loadGlobals = new Set<IdentifierId>();
|
||||
|
||||
@@ -88,18 +79,19 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
const reactiveIds = inferReactiveIdentifiers(fn);
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
if (block.terminal.kind === 'scope') {
|
||||
if (
|
||||
block.terminal.kind === 'scope' ||
|
||||
block.terminal.kind === 'pruned-scope'
|
||||
) {
|
||||
const scopeBlock = fn.body.blocks.get(block.terminal.block)!;
|
||||
if (
|
||||
scopeBlock.instructions.length === 1 &&
|
||||
scopeBlock.terminal.kind === 'goto' &&
|
||||
scopeBlock.terminal.block === block.terminal.fallthrough
|
||||
) {
|
||||
scopeInfos.set(
|
||||
block.terminal.scope.id,
|
||||
block.terminal.scope.dependencies,
|
||||
);
|
||||
}
|
||||
scopeInfos.set(block.terminal.scope.id, {
|
||||
pruned: block.terminal.kind === 'pruned-scope',
|
||||
deps: block.terminal.scope.dependencies,
|
||||
hasSingleInstr:
|
||||
scopeBlock.instructions.length === 1 &&
|
||||
scopeBlock.terminal.kind === 'goto' &&
|
||||
scopeBlock.terminal.block === block.terminal.fallthrough,
|
||||
});
|
||||
}
|
||||
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
|
||||
for (const instr of block.instructions) {
|
||||
@@ -181,12 +173,22 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
fnExpr.lvalue.identifier.scope != null
|
||||
? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
|
||||
: null;
|
||||
let minimalDeps: Set<ReactiveScopeDependency>;
|
||||
if (scopeInfo != null) {
|
||||
minimalDeps = new Set(scopeInfo);
|
||||
} else {
|
||||
minimalDeps = inferMinimalDependencies(fnExpr);
|
||||
CompilerError.invariant(scopeInfo != null, {
|
||||
reason: 'Expected function expression scope to exist',
|
||||
loc: value.loc,
|
||||
});
|
||||
if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) {
|
||||
/**
|
||||
* TODO: retry pipeline that ensures effect function expressions
|
||||
* are placed into their own scope
|
||||
*/
|
||||
CompilerError.throwTodo({
|
||||
reason:
|
||||
'[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction',
|
||||
loc: fnExpr.loc,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: push dependencies to the effect deps array
|
||||
*
|
||||
@@ -194,9 +196,8 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
|
||||
* explanation.
|
||||
*/
|
||||
|
||||
const usedDeps = [];
|
||||
for (const dep of minimalDeps) {
|
||||
for (const dep of scopeInfo.deps) {
|
||||
if (
|
||||
((isUseRefType(dep.identifier) ||
|
||||
isSetStateType(dep.identifier)) &&
|
||||
@@ -421,132 +422,3 @@ function collectDepUsages(
|
||||
|
||||
return sourceLocations;
|
||||
}
|
||||
|
||||
function inferMinimalDependencies(
|
||||
fnInstr: TInstruction<FunctionExpression>,
|
||||
): Set<ReactiveScopeDependency> {
|
||||
const fn = fnInstr.value.loweredFunc.func;
|
||||
|
||||
const temporaries = collectTemporariesSidemap(fn, new Set());
|
||||
const {
|
||||
hoistableObjects,
|
||||
processedInstrsInOptional,
|
||||
temporariesReadInOptional,
|
||||
} = collectOptionalChainSidemap(fn);
|
||||
|
||||
const hoistablePropertyLoads = collectHoistablePropertyLoadsInInnerFn(
|
||||
fnInstr,
|
||||
temporaries,
|
||||
hoistableObjects,
|
||||
);
|
||||
const hoistableToFnEntry = hoistablePropertyLoads.get(fn.body.entry);
|
||||
CompilerError.invariant(hoistableToFnEntry != null, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: missing entry block',
|
||||
loc: fnInstr.loc,
|
||||
});
|
||||
|
||||
const dependencies = inferDependencies(
|
||||
fnInstr,
|
||||
new Map([...temporaries, ...temporariesReadInOptional]),
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
|
||||
const tree = new ReactiveScopeDependencyTreeHIR(
|
||||
[...hoistableToFnEntry.assumedNonNullObjects].map(o => o.fullPath),
|
||||
);
|
||||
for (const dep of dependencies) {
|
||||
tree.addDependency({...dep});
|
||||
}
|
||||
|
||||
return tree.deriveMinimalDependencies();
|
||||
}
|
||||
|
||||
function inferDependencies(
|
||||
fnInstr: TInstruction<FunctionExpression>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
|
||||
): Set<ReactiveScopeDependency> {
|
||||
const fn = fnInstr.value.loweredFunc.func;
|
||||
const context = new DependencyCollectionContext(
|
||||
new Set(),
|
||||
temporaries,
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
for (const dep of fn.context) {
|
||||
context.declare(dep.identifier, {
|
||||
id: makeInstructionId(0),
|
||||
scope: empty(),
|
||||
});
|
||||
}
|
||||
const placeholderScope: ReactiveScope = {
|
||||
id: makeScopeId(0),
|
||||
range: {
|
||||
start: fnInstr.id,
|
||||
end: makeInstructionId(fnInstr.id + 1),
|
||||
},
|
||||
dependencies: new Set(),
|
||||
reassignments: new Set(),
|
||||
declarations: new Map(),
|
||||
earlyReturnValue: null,
|
||||
merged: new Set(),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
context.enterScope(placeholderScope);
|
||||
inferDependenciesInFn(fn, context, temporaries);
|
||||
context.exitScope(placeholderScope, false);
|
||||
const resultUnfiltered = context.deps.get(placeholderScope);
|
||||
CompilerError.invariant(resultUnfiltered != null, {
|
||||
reason:
|
||||
'[InferEffectDependencies] Internal invariant broken: missing scope dependencies',
|
||||
loc: fn.loc,
|
||||
});
|
||||
|
||||
const fnContext = new Set(fn.context.map(dep => dep.identifier.id));
|
||||
const result = new Set<ReactiveScopeDependency>();
|
||||
for (const dep of resultUnfiltered) {
|
||||
if (fnContext.has(dep.identifier.id)) {
|
||||
result.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function inferDependenciesInFn(
|
||||
fn: HIRFunction,
|
||||
context: DependencyCollectionContext,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
// Record referenced optional chains in phis
|
||||
for (const phi of block.phis) {
|
||||
for (const operand of phi.operands) {
|
||||
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
|
||||
if (maybeOptionalChain) {
|
||||
context.visitDependency(maybeOptionalChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
context.declare(instr.lvalue.identifier, {
|
||||
id: instr.id,
|
||||
scope: context.currentScope,
|
||||
});
|
||||
/**
|
||||
* Recursively visit the inner function to extract dependencies
|
||||
*/
|
||||
const innerFn = instr.value.loweredFunc.func;
|
||||
context.enterInnerFn(instr as TInstruction<FunctionExpression>, () => {
|
||||
inferDependenciesInFn(innerFn, context, temporaries);
|
||||
});
|
||||
} else {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0].value));
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import { useEffect } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const { foo } = t0;
|
||||
const arr = [];
|
||||
|
||||
useEffect(() => print(arr[0].value), [arr[0].value]);
|
||||
arr.push({ value: foo });
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,12 +0,0 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
// Taking either arr[0].value or arr as a dependency is reasonable
|
||||
// as long as developers know what to expect.
|
||||
useEffect(() => print(arr[0].value));
|
||||
arr.push({value: foo});
|
||||
return arr;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({arrRef}) {
|
||||
// Avoid taking arr.current as a dependency
|
||||
useEffect(() => print(arrRef.current));
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { print } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const { arrRef } = t0;
|
||||
|
||||
useEffect(() => print(arrRef.current), [arrRef]);
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,11 +0,0 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
|
||||
import {useEffect, useRef} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function Component({arrRef}) {
|
||||
// Avoid taking arr.current as a dependency
|
||||
useEffect(() => print(arrRef.current));
|
||||
arrRef.current.val = 2;
|
||||
return arrRef;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffect(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import { useEffect } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const { foo } = t0;
|
||||
const arr = [];
|
||||
useEffect(() => arr.push(foo), [arr, foo]);
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,9 +0,0 @@
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffect(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useRef} from 'react';
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* The retry pipeline disables memoization features, which means we need to
|
||||
* provide an alternate implementation of effect dependencies which does not
|
||||
* rely on memoization.
|
||||
*/
|
||||
function useFoo({cond}) {
|
||||
const ref = useRef();
|
||||
const derived = cond ? ref.current : makeObject();
|
||||
useSpecialEffect(() => {
|
||||
log(derived);
|
||||
}, [derived]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
11 | const ref = useRef();
|
||||
12 | const derived = cond ? ref.current : makeObject();
|
||||
> 13 | useSpecialEffect(() => {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 14 | log(derived);
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 15 | }, [derived]);
|
||||
| ^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Invariant: Expected function expression scope to exist (13:15)) (13:15)
|
||||
16 | return ref;
|
||||
17 | }
|
||||
18 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useRef} from 'react';
|
||||
import {useSpecialEffect} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* The retry pipeline disables memoization features, which means we need to
|
||||
* provide an alternate implementation of effect dependencies which does not
|
||||
* rely on memoization.
|
||||
*/
|
||||
function useFoo({cond}) {
|
||||
const ref = useRef();
|
||||
const derived = cond ? ref.current : makeObject();
|
||||
useSpecialEffect(() => {
|
||||
log(derived);
|
||||
}, [derived]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import { useRef } from "react";
|
||||
import { useSpecialEffect } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* The retry pipeline disables memoization features, which means we need to
|
||||
* provide an alternate implementation of effect dependencies which does not
|
||||
* rely on memoization.
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const { cond } = t0;
|
||||
const ref = useRef();
|
||||
const derived = cond ? ref.current : makeObject();
|
||||
useSpecialEffect(
|
||||
() => {
|
||||
log(derived);
|
||||
},
|
||||
[derived],
|
||||
[derived],
|
||||
);
|
||||
return ref;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import {renderToPipeableStream} from 'react-dom/server';
|
||||
import {Writable} from 'stream';
|
||||
|
||||
import App from '../src/components/App';
|
||||
|
||||
@@ -15,41 +14,11 @@ if (process.env.NODE_ENV === 'development') {
|
||||
assets = require('../build/asset-manifest.json');
|
||||
}
|
||||
|
||||
class ThrottledWritable extends Writable {
|
||||
constructor(destination) {
|
||||
super();
|
||||
this.destination = destination;
|
||||
this.delay = 150;
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
let o = 0;
|
||||
const write = () => {
|
||||
this.destination.write(chunk.slice(o, o + 100), encoding, x => {
|
||||
o += 100;
|
||||
if (o < chunk.length) {
|
||||
setTimeout(write, this.delay);
|
||||
} else {
|
||||
callback(x);
|
||||
}
|
||||
});
|
||||
};
|
||||
setTimeout(write, this.delay);
|
||||
}
|
||||
|
||||
_final(callback) {
|
||||
setTimeout(() => {
|
||||
this.destination.end(callback);
|
||||
}, this.delay);
|
||||
}
|
||||
}
|
||||
|
||||
export default function render(url, res) {
|
||||
res.socket.on('error', error => {
|
||||
// Log fatal errors
|
||||
console.error('Fatal', error);
|
||||
});
|
||||
console.log('hello');
|
||||
let didError = false;
|
||||
const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
|
||||
bootstrapScripts: [assets['main.js']],
|
||||
@@ -57,10 +26,7 @@ export default function render(url, res) {
|
||||
// If something errored before we started streaming, we set the error code appropriately.
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
res.setHeader('Content-type', 'text/html');
|
||||
// To test the actual chunks taking time to load over the network, we throttle
|
||||
// the stream a bit.
|
||||
const throttledResponse = new ThrottledWritable(res);
|
||||
pipe(throttledResponse);
|
||||
pipe(res);
|
||||
},
|
||||
onShellError(x) {
|
||||
// Something errored before we could complete the shell so we emit an alternative shell.
|
||||
|
||||
@@ -37,7 +37,6 @@ export default class Chrome extends Component {
|
||||
</div>
|
||||
</Theme.Provider>
|
||||
</Suspense>
|
||||
<p>This should appear in the first paint.</p>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `assetManifest = ${JSON.stringify(assets)};`,
|
||||
|
||||
@@ -120,13 +120,12 @@ const ScriptStreamingFormat: StreamingFormat = 0;
|
||||
const DataStreamingFormat: StreamingFormat = 1;
|
||||
|
||||
export type InstructionState = number;
|
||||
const NothingSent /* */ = 0b000000;
|
||||
const SentCompleteSegmentFunction /* */ = 0b000001;
|
||||
const SentCompleteBoundaryFunction /* */ = 0b000010;
|
||||
const SentClientRenderFunction /* */ = 0b000100;
|
||||
const SentStyleInsertionFunction /* */ = 0b001000;
|
||||
const SentFormReplayingRuntime /* */ = 0b010000;
|
||||
const SentCompletedShellId /* */ = 0b100000;
|
||||
const NothingSent /* */ = 0b00000;
|
||||
const SentCompleteSegmentFunction /* */ = 0b00001;
|
||||
const SentCompleteBoundaryFunction /* */ = 0b00010;
|
||||
const SentClientRenderFunction /* */ = 0b00100;
|
||||
const SentStyleInsertionFunction /* */ = 0b01000;
|
||||
const SentFormReplayingRuntime /* */ = 0b10000;
|
||||
|
||||
// Per request, global state that is not contextual to the rendering subtree.
|
||||
// This cannot be resumed and therefore should only contain things that are
|
||||
@@ -290,15 +289,15 @@ export type ResumableState = {
|
||||
|
||||
const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');
|
||||
|
||||
const startInlineScript = stringToPrecomputedChunk('<script');
|
||||
const startInlineScript = stringToPrecomputedChunk('<script>');
|
||||
const endInlineScript = stringToPrecomputedChunk('</script>');
|
||||
|
||||
const startScriptSrc = stringToPrecomputedChunk('<script src="');
|
||||
const startModuleSrc = stringToPrecomputedChunk('<script type="module" src="');
|
||||
const scriptNonce = stringToPrecomputedChunk(' nonce="');
|
||||
const scriptIntegirty = stringToPrecomputedChunk(' integrity="');
|
||||
const scriptCrossOrigin = stringToPrecomputedChunk(' crossorigin="');
|
||||
const endAsyncScript = stringToPrecomputedChunk(' async=""></script>');
|
||||
const scriptNonce = stringToPrecomputedChunk('" nonce="');
|
||||
const scriptIntegirty = stringToPrecomputedChunk('" integrity="');
|
||||
const scriptCrossOrigin = stringToPrecomputedChunk('" crossorigin="');
|
||||
const endAsyncScript = stringToPrecomputedChunk('" async=""></script>');
|
||||
|
||||
/**
|
||||
* This escaping function is designed to work with with inline scripts where the entire
|
||||
@@ -368,7 +367,7 @@ export function createRenderState(
|
||||
nonce === undefined
|
||||
? startInlineScript
|
||||
: stringToPrecomputedChunk(
|
||||
'<script nonce="' + escapeTextForBrowser(nonce) + '"',
|
||||
'<script nonce="' + escapeTextForBrowser(nonce) + '">',
|
||||
);
|
||||
const idPrefix = resumableState.idPrefix;
|
||||
|
||||
@@ -377,10 +376,8 @@ export function createRenderState(
|
||||
const {bootstrapScriptContent, bootstrapScripts, bootstrapModules} =
|
||||
resumableState;
|
||||
if (bootstrapScriptContent !== undefined) {
|
||||
bootstrapChunks.push(inlineScriptWithNonce);
|
||||
pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
|
||||
bootstrapChunks.push(
|
||||
endOfStartTag,
|
||||
inlineScriptWithNonce,
|
||||
stringToChunk(escapeEntireInlineScriptContent(bootstrapScriptContent)),
|
||||
endInlineScript,
|
||||
);
|
||||
@@ -530,30 +527,25 @@ export function createRenderState(
|
||||
bootstrapChunks.push(
|
||||
startScriptSrc,
|
||||
stringToChunk(escapeTextForBrowser(src)),
|
||||
attributeEnd,
|
||||
);
|
||||
if (nonce) {
|
||||
bootstrapChunks.push(
|
||||
scriptNonce,
|
||||
stringToChunk(escapeTextForBrowser(nonce)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
if (typeof integrity === 'string') {
|
||||
bootstrapChunks.push(
|
||||
scriptIntegirty,
|
||||
stringToChunk(escapeTextForBrowser(integrity)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
if (typeof crossOrigin === 'string') {
|
||||
bootstrapChunks.push(
|
||||
scriptCrossOrigin,
|
||||
stringToChunk(escapeTextForBrowser(crossOrigin)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
|
||||
bootstrapChunks.push(endAsyncScript);
|
||||
}
|
||||
}
|
||||
@@ -587,30 +579,26 @@ export function createRenderState(
|
||||
bootstrapChunks.push(
|
||||
startModuleSrc,
|
||||
stringToChunk(escapeTextForBrowser(src)),
|
||||
attributeEnd,
|
||||
);
|
||||
|
||||
if (nonce) {
|
||||
bootstrapChunks.push(
|
||||
scriptNonce,
|
||||
stringToChunk(escapeTextForBrowser(nonce)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
if (typeof integrity === 'string') {
|
||||
bootstrapChunks.push(
|
||||
scriptIntegirty,
|
||||
stringToChunk(escapeTextForBrowser(integrity)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
if (typeof crossOrigin === 'string') {
|
||||
bootstrapChunks.push(
|
||||
scriptCrossOrigin,
|
||||
stringToChunk(escapeTextForBrowser(crossOrigin)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
|
||||
bootstrapChunks.push(endAsyncScript);
|
||||
}
|
||||
}
|
||||
@@ -1972,32 +1960,11 @@ function injectFormReplayingRuntime(
|
||||
(!enableFizzExternalRuntime || !renderState.externalRuntimeScript)
|
||||
) {
|
||||
resumableState.instructions |= SentFormReplayingRuntime;
|
||||
const preamble = renderState.preamble;
|
||||
const bootstrapChunks = renderState.bootstrapChunks;
|
||||
if (
|
||||
(preamble.htmlChunks || preamble.headChunks) &&
|
||||
bootstrapChunks.length === 0
|
||||
) {
|
||||
// If we rendered the whole document, then we emitted a rel="expect" that needs a
|
||||
// matching target. If we haven't emitted that yet, we need to include it in this
|
||||
// script tag.
|
||||
bootstrapChunks.push(renderState.startInlineScript);
|
||||
pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
|
||||
bootstrapChunks.push(
|
||||
endOfStartTag,
|
||||
formReplayingRuntimeScript,
|
||||
endInlineScript,
|
||||
);
|
||||
} else {
|
||||
// Otherwise we added to the beginning of the scripts. This will mean that it
|
||||
// appears before the shell ID unfortunately.
|
||||
bootstrapChunks.unshift(
|
||||
renderState.startInlineScript,
|
||||
endOfStartTag,
|
||||
formReplayingRuntimeScript,
|
||||
endInlineScript,
|
||||
);
|
||||
}
|
||||
renderState.bootstrapChunks.unshift(
|
||||
renderState.startInlineScript,
|
||||
formReplayingRuntimeScript,
|
||||
endInlineScript,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4108,21 +4075,8 @@ function writeBootstrap(
|
||||
|
||||
export function writeCompletedRoot(
|
||||
destination: Destination,
|
||||
resumableState: ResumableState,
|
||||
renderState: RenderState,
|
||||
): boolean {
|
||||
const preamble = renderState.preamble;
|
||||
if (preamble.htmlChunks || preamble.headChunks) {
|
||||
// If we rendered the whole document, then we emitted a rel="expect" that needs a
|
||||
// matching target. Normally we use one of the bootstrap scripts for this but if
|
||||
// there are none, then we need to emit a tag to complete the shell.
|
||||
if ((resumableState.instructions & SentCompletedShellId) === NothingSent) {
|
||||
const bootstrapChunks = renderState.bootstrapChunks;
|
||||
bootstrapChunks.push(startChunkForTag('template'));
|
||||
pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
|
||||
bootstrapChunks.push(endOfStartTag, endChunkForTag('template'));
|
||||
}
|
||||
}
|
||||
return writeBootstrap(destination, renderState);
|
||||
}
|
||||
|
||||
@@ -4446,7 +4400,6 @@ export function writeCompletedSegmentInstruction(
|
||||
resumableState.streamingFormat === ScriptStreamingFormat;
|
||||
if (scriptFormat) {
|
||||
writeChunk(destination, renderState.startInlineScript);
|
||||
writeChunk(destination, endOfStartTag);
|
||||
if (
|
||||
(resumableState.instructions & SentCompleteSegmentFunction) ===
|
||||
NothingSent
|
||||
@@ -4528,7 +4481,6 @@ export function writeCompletedBoundaryInstruction(
|
||||
resumableState.streamingFormat === ScriptStreamingFormat;
|
||||
if (scriptFormat) {
|
||||
writeChunk(destination, renderState.startInlineScript);
|
||||
writeChunk(destination, endOfStartTag);
|
||||
if (requiresStyleInsertion) {
|
||||
if (
|
||||
(resumableState.instructions & SentCompleteBoundaryFunction) ===
|
||||
@@ -4639,7 +4591,6 @@ export function writeClientRenderBoundaryInstruction(
|
||||
resumableState.streamingFormat === ScriptStreamingFormat;
|
||||
if (scriptFormat) {
|
||||
writeChunk(destination, renderState.startInlineScript);
|
||||
writeChunk(destination, endOfStartTag);
|
||||
if (
|
||||
(resumableState.instructions & SentClientRenderFunction) ===
|
||||
NothingSent
|
||||
@@ -4982,44 +4933,6 @@ function preloadLateStyles(this: Destination, styleQueue: StyleQueue) {
|
||||
styleQueue.sheets.clear();
|
||||
}
|
||||
|
||||
const blockingRenderChunkStart = stringToPrecomputedChunk(
|
||||
'<link rel="expect" href="#',
|
||||
);
|
||||
const blockingRenderChunkEnd = stringToPrecomputedChunk(
|
||||
'" blocking="render"/>',
|
||||
);
|
||||
|
||||
function writeBlockingRenderInstruction(
|
||||
destination: Destination,
|
||||
resumableState: ResumableState,
|
||||
renderState: RenderState,
|
||||
): void {
|
||||
const idPrefix = resumableState.idPrefix;
|
||||
const shellId = '\u00AB' + idPrefix + 'R\u00BB';
|
||||
writeChunk(destination, blockingRenderChunkStart);
|
||||
writeChunk(destination, stringToChunk(escapeTextForBrowser(shellId)));
|
||||
writeChunk(destination, blockingRenderChunkEnd);
|
||||
}
|
||||
|
||||
const completedShellIdAttributeStart = stringToPrecomputedChunk(' id="');
|
||||
|
||||
function pushCompletedShellIdAttribute(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
resumableState: ResumableState,
|
||||
): void {
|
||||
if ((resumableState.instructions & SentCompletedShellId) !== NothingSent) {
|
||||
return;
|
||||
}
|
||||
resumableState.instructions |= SentCompletedShellId;
|
||||
const idPrefix = resumableState.idPrefix;
|
||||
const shellId = '\u00AB' + idPrefix + 'R\u00BB';
|
||||
target.push(
|
||||
completedShellIdAttributeStart,
|
||||
stringToChunk(escapeTextForBrowser(shellId)),
|
||||
attributeEnd,
|
||||
);
|
||||
}
|
||||
|
||||
// We don't bother reporting backpressure at the moment because we expect to
|
||||
// flush the entire preamble in a single pass. This probably should be modified
|
||||
// in the future to be backpressure sensitive but that requires a larger refactor
|
||||
@@ -5029,7 +4942,6 @@ export function writePreambleStart(
|
||||
resumableState: ResumableState,
|
||||
renderState: RenderState,
|
||||
willFlushAllSegments: boolean,
|
||||
skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
|
||||
): void {
|
||||
// This function must be called exactly once on every request
|
||||
if (
|
||||
@@ -5115,16 +5027,6 @@ export function writePreambleStart(
|
||||
renderState.bulkPreloads.forEach(flushResource, destination);
|
||||
renderState.bulkPreloads.clear();
|
||||
|
||||
if ((htmlChunks || headChunks) && !skipExpect) {
|
||||
// If we have any html or head chunks we know that we're rendering a full document.
|
||||
// A full document should block display until the full shell has downloaded.
|
||||
// Therefore we insert a render blocking instruction referring to the last body
|
||||
// element that's considered part of the shell. We do this after the important loads
|
||||
// have already been emitted so we don't do anything to delay them but early so that
|
||||
// the browser doesn't risk painting too early.
|
||||
writeBlockingRenderInstruction(destination, resumableState, renderState);
|
||||
}
|
||||
|
||||
// Write embedding hoistableChunks
|
||||
const hoistableChunks = renderState.hoistableChunks;
|
||||
for (i = 0; i < hoistableChunks.length; i++) {
|
||||
|
||||
@@ -3580,8 +3580,7 @@ describe('ReactDOMFizzServer', () => {
|
||||
expect(document.head.innerHTML).toBe(
|
||||
'<script type="importmap">' +
|
||||
JSON.stringify(importMap) +
|
||||
'</script><script async="" src="foo"></script>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render">',
|
||||
'</script><script async="" src="foo"></script>',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4190,7 +4189,7 @@ describe('ReactDOMFizzServer', () => {
|
||||
renderOptions.unstable_externalRuntimeSrc,
|
||||
).map(n => n.outerHTML),
|
||||
).toEqual([
|
||||
'<script src="foo" id="«R»" async=""></script>',
|
||||
'<script src="foo" async=""></script>',
|
||||
'<script src="bar" async=""></script>',
|
||||
'<script src="baz" integrity="qux" async=""></script>',
|
||||
'<script type="module" src="quux" async=""></script>',
|
||||
@@ -4277,7 +4276,7 @@ describe('ReactDOMFizzServer', () => {
|
||||
renderOptions.unstable_externalRuntimeSrc,
|
||||
).map(n => n.outerHTML),
|
||||
).toEqual([
|
||||
'<script src="foo" id="«R»" async=""></script>',
|
||||
'<script src="foo" async=""></script>',
|
||||
'<script src="bar" async=""></script>',
|
||||
'<script src="baz" crossorigin="" async=""></script>',
|
||||
'<script src="qux" crossorigin="" async=""></script>',
|
||||
@@ -4513,7 +4512,7 @@ describe('ReactDOMFizzServer', () => {
|
||||
|
||||
// the html should be as-is
|
||||
expect(document.documentElement.innerHTML).toEqual(
|
||||
'<head><link rel="expect" href="#«R»" blocking="render"></head><body><p>hello world!</p><template id="«R»"></template></body>',
|
||||
'<head></head><body><p>hello world!</p></body>',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6493,7 +6492,7 @@ describe('ReactDOMFizzServer', () => {
|
||||
});
|
||||
|
||||
expect(document.documentElement.outerHTML).toEqual(
|
||||
'<html><head><link rel="expect" href="#«R»" blocking="render"></head><body><script>try { foo() } catch (e) {} ;</script><template id="«R»"></template></body></html>',
|
||||
'<html><head></head><body><script>try { foo() } catch (e) {} ;</script></body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script id="«R»">INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -529,7 +529,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
|
||||
const result = await readResult(stream);
|
||||
expect(result).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/><title>foo</title></head><body>bar<template id="«R»"></template></body></html>',
|
||||
'<!DOCTYPE html><html><head><title>foo</title></head><body>bar</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -547,7 +547,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
// TODO: remove interpolation because it prevents snapshot updates.
|
||||
// eslint-disable-next-line jest/no-interpolation-in-snapshots
|
||||
`"<link rel="preload" as="script" fetchPriority="low" nonce="R4nd0m" href="init.js"/><link rel="modulepreload" fetchPriority="low" nonce="R4nd0m" href="init.mjs"/><div>hello world</div><script nonce="${nonce}" id="«R»">INIT();</script><script src="init.js" nonce="${nonce}" async=""></script><script type="module" src="init.mjs" nonce="${nonce}" async=""></script>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" nonce="R4nd0m" href="init.js"/><link rel="modulepreload" fetchPriority="low" nonce="R4nd0m" href="init.mjs"/><div>hello world</div><script nonce="${nonce}">INIT();</script><script src="init.js" nonce="${nonce}" async=""></script><script type="module" src="init.mjs" nonce="${nonce}" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('ReactDOMFizzServerEdge', () => {
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body><main>hello</main><template id="«R»"></template></body></html>"`,
|
||||
`"<!DOCTYPE html><html><head></head><body><main>hello</main></body></html>"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('ReactDOMFizzServerNode', () => {
|
||||
});
|
||||
// with Float, we emit empty heads if they are elided when rendering <html>
|
||||
expect(output.result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('ReactDOMFizzServerNode', () => {
|
||||
pipe(writable);
|
||||
});
|
||||
expect(output.result).toMatchInlineSnapshot(
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script id="«R»">INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -106,10 +106,7 @@ describe('ReactDOMFizzStatic', () => {
|
||||
node.tagName !== 'TEMPLATE' &&
|
||||
node.tagName !== 'template' &&
|
||||
!node.hasAttribute('hidden') &&
|
||||
!node.hasAttribute('aria-hidden') &&
|
||||
// Ignore the render blocking expect
|
||||
(node.getAttribute('rel') !== 'expect' ||
|
||||
node.getAttribute('blocking') !== 'render')
|
||||
!node.hasAttribute('aria-hidden')
|
||||
) {
|
||||
const props = {};
|
||||
const attributes = node.attributes;
|
||||
|
||||
@@ -187,7 +187,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -201,7 +201,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script id="«R»">INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1428,8 +1428,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
expect(await readContent(content)).toBe(
|
||||
'<!DOCTYPE html><html lang="en"><head>' +
|
||||
'<link rel="stylesheet" href="my-style" data-precedence="high"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body>Hello<template id="«R»"></template></body></html>',
|
||||
'</head><body>Hello</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1475,8 +1474,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
expect(await readContent(content)).toBe(
|
||||
'<!DOCTYPE html><html lang="en"><head>' +
|
||||
'<link rel="stylesheet" href="my-style" data-precedence="high"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body>Hello<template id="«R»"></template></body></html>',
|
||||
'</head><body>Hello</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1527,8 +1525,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
expect(await readContent(content)).toBe(
|
||||
'<!DOCTYPE html><html><head>' +
|
||||
'<link rel="stylesheet" href="my-style" data-precedence="high"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><div>Hello</div><template id="«R»"></template></body></html>',
|
||||
'</head><body><div>Hello</div></body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1610,8 +1607,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
let result = decoder.decode(value, {stream: true});
|
||||
|
||||
expect(result).toBe(
|
||||
'<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body>hello<!--$?--><template id="B:1"></template><!--/$--><template id="«R»"></template>',
|
||||
'<!DOCTYPE html><html><head></head><body>hello<!--$?--><template id="B:1"></template><!--/$-->',
|
||||
);
|
||||
|
||||
await 1;
|
||||
@@ -1635,9 +1631,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
const slice = result.slice(0, instructionIndex + '$RC'.length);
|
||||
|
||||
expect(slice).toBe(
|
||||
'<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body>hello<!--$?--><template id="B:1"></template><!--/$--><template id="«R»"></template>' +
|
||||
'<div hidden id="S:1">world<!-- --></div><script>$RC',
|
||||
'<!DOCTYPE html><html><head></head><body>hello<!--$?--><template id="B:1"></template><!--/$--><div hidden id="S:1">world<!-- --></div><script>$RC',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('ReactDOMFizzStaticNode', () => {
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('ReactDOMFizzStaticNode', () => {
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script id="«R»">INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -250,10 +250,7 @@ describe('ReactDOMFloat', () => {
|
||||
node.tagName !== 'TEMPLATE' &&
|
||||
node.tagName !== 'template' &&
|
||||
!node.hasAttribute('hidden') &&
|
||||
!node.hasAttribute('aria-hidden') &&
|
||||
// Ignore the render blocking expect
|
||||
(node.getAttribute('rel') !== 'expect' ||
|
||||
node.getAttribute('blocking') !== 'render'))
|
||||
!node.hasAttribute('aria-hidden'))
|
||||
) {
|
||||
const props = {};
|
||||
const attributes = node.attributes;
|
||||
@@ -693,9 +690,7 @@ describe('ReactDOMFloat', () => {
|
||||
pipe(writable);
|
||||
});
|
||||
expect(chunks).toEqual([
|
||||
'<!DOCTYPE html><html><head><script async="" src="foo"></script>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/><title>foo</title></head>' +
|
||||
'<body>bar<template id="«R»"></template>',
|
||||
'<!DOCTYPE html><html><head><script async="" src="foo"></script><title>foo</title></head><body>bar',
|
||||
'</body></html>',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -34,8 +34,7 @@ describe('ReactDOMFloat', () => {
|
||||
);
|
||||
|
||||
expect(result).toEqual(
|
||||
'<html><head><meta charSet="utf-8"/><link rel="expect" href="#«R»" blocking="render"/>' +
|
||||
'<title>title</title><script src="foo"></script></head><template id="«R»"></template></html>',
|
||||
'<html><head><meta charSet="utf-8"/><title>title</title><script src="foo"></script></head></html>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,10 +104,7 @@ describe('ReactDOM HostSingleton', () => {
|
||||
el.tagName !== 'TEMPLATE' &&
|
||||
el.tagName !== 'template' &&
|
||||
!el.hasAttribute('hidden') &&
|
||||
!el.hasAttribute('aria-hidden') &&
|
||||
// Ignore the render blocking expect
|
||||
(node.getAttribute('rel') !== 'expect' ||
|
||||
node.getAttribute('blocking') !== 'render')) ||
|
||||
!el.hasAttribute('aria-hidden')) ||
|
||||
el.hasAttribute('data-meaningful')
|
||||
) {
|
||||
const props = {};
|
||||
|
||||
@@ -77,16 +77,12 @@ describe('rendering React components at document', () => {
|
||||
await act(() => {
|
||||
root = ReactDOMClient.hydrateRoot(testDocument, <Root hello="world" />);
|
||||
});
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
|
||||
await act(() => {
|
||||
root.render(<Root hello="moon" />);
|
||||
});
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello moon' + '<template id="«R»"></template>',
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello moon');
|
||||
|
||||
expect(body === testDocument.body).toBe(true);
|
||||
});
|
||||
@@ -111,9 +107,7 @@ describe('rendering React components at document', () => {
|
||||
await act(() => {
|
||||
root = ReactDOMClient.hydrateRoot(testDocument, <Root />);
|
||||
});
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
|
||||
const originalDocEl = testDocument.documentElement;
|
||||
const originalHead = testDocument.head;
|
||||
@@ -124,10 +118,8 @@ describe('rendering React components at document', () => {
|
||||
expect(testDocument.firstChild).toBe(originalDocEl);
|
||||
expect(testDocument.head).toBe(originalHead);
|
||||
expect(testDocument.body).toBe(originalBody);
|
||||
expect(originalBody.innerHTML).toBe('<template id="«R»"></template>');
|
||||
expect(originalHead.innerHTML).toBe(
|
||||
'<link rel="expect" href="#«R»" blocking="render">',
|
||||
);
|
||||
expect(originalBody.firstChild).toEqual(null);
|
||||
expect(originalHead.firstChild).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not be able to switch root constructors', async () => {
|
||||
@@ -165,17 +157,13 @@ describe('rendering React components at document', () => {
|
||||
root = ReactDOMClient.hydrateRoot(testDocument, <Component />);
|
||||
});
|
||||
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
|
||||
await act(() => {
|
||||
root.render(<Component2 />);
|
||||
});
|
||||
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'<template id="«R»"></template>' + 'Goodbye world',
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe('Goodbye world');
|
||||
});
|
||||
|
||||
it('should be able to mount into document', async () => {
|
||||
@@ -204,9 +192,7 @@ describe('rendering React components at document', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('cannot render over an existing text child at the root', async () => {
|
||||
@@ -339,9 +325,7 @@ describe('rendering React components at document', () => {
|
||||
: [],
|
||||
);
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
favorSafetyOverHydrationPerf
|
||||
? 'Hello world'
|
||||
: 'Goodbye world<template id="«R»"></template>',
|
||||
favorSafetyOverHydrationPerf ? 'Hello world' : 'Goodbye world',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -150,10 +150,7 @@ function getVisibleChildren(element: Element): React$Node {
|
||||
node.tagName !== 'TEMPLATE' &&
|
||||
node.tagName !== 'template' &&
|
||||
!node.hasAttribute('hidden') &&
|
||||
!node.hasAttribute('aria-hidden') &&
|
||||
// Ignore the render blocking expect
|
||||
(node.getAttribute('rel') !== 'expect' ||
|
||||
node.getAttribute('blocking') !== 'render')
|
||||
!node.hasAttribute('aria-hidden')
|
||||
) {
|
||||
const props: any = {};
|
||||
const attributes = node.attributes;
|
||||
|
||||
@@ -17,10 +17,7 @@ import type {
|
||||
FormatContext,
|
||||
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
||||
|
||||
import {
|
||||
pushStartInstance as pushStartInstanceImpl,
|
||||
writePreambleStart as writePreambleStartImpl,
|
||||
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
||||
import {pushStartInstance as pushStartInstanceImpl} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
||||
|
||||
import type {
|
||||
Destination,
|
||||
@@ -65,11 +62,13 @@ export {
|
||||
writeEndPendingSuspenseBoundary,
|
||||
writeHoistablesForBoundary,
|
||||
writePlaceholder,
|
||||
writeCompletedRoot,
|
||||
createRootFormatContext,
|
||||
createRenderState,
|
||||
createResumableState,
|
||||
createPreambleState,
|
||||
createHoistableState,
|
||||
writePreambleStart,
|
||||
writePreambleEnd,
|
||||
writeHoistables,
|
||||
writePostamble,
|
||||
@@ -204,30 +203,5 @@ export function writeEndClientRenderedSuspenseBoundary(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function writePreambleStart(
|
||||
destination: Destination,
|
||||
resumableState: ResumableState,
|
||||
renderState: RenderState,
|
||||
willFlushAllSegments: boolean,
|
||||
skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
|
||||
): void {
|
||||
return writePreambleStartImpl(
|
||||
destination,
|
||||
resumableState,
|
||||
renderState,
|
||||
willFlushAllSegments,
|
||||
true, // skipExpect
|
||||
);
|
||||
}
|
||||
|
||||
export function writeCompletedRoot(
|
||||
destination: Destination,
|
||||
resumableState: ResumableState,
|
||||
renderState: RenderState,
|
||||
): boolean {
|
||||
// Markup doesn't have any bootstrap scripts nor shell completions.
|
||||
return true;
|
||||
}
|
||||
|
||||
export type TransitionStatus = FormStatus;
|
||||
export const NotPendingTransition: TransitionStatus = NotPending;
|
||||
|
||||
@@ -1062,9 +1062,9 @@ function commitTransitionProgress(offscreenFiber: Fiber) {
|
||||
if (
|
||||
parent !== null &&
|
||||
parent.tag === SuspenseComponent &&
|
||||
parent.memoizedProps.name
|
||||
parent.memoizedProps.unstable_name
|
||||
) {
|
||||
name = parent.memoizedProps.name;
|
||||
name = parent.memoizedProps.unstable_name;
|
||||
}
|
||||
|
||||
if (!wasHidden && isHidden) {
|
||||
@@ -4952,7 +4952,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
|
||||
if (transitions !== null) {
|
||||
const abortReason = {
|
||||
reason: 'suspense',
|
||||
name: current.memoizedProps.name || null,
|
||||
name: current.memoizedProps.unstable_name || null,
|
||||
};
|
||||
if (
|
||||
current.memoizedState === null ||
|
||||
|
||||
@@ -412,7 +412,7 @@ describe('ReactInteractionTracing', () => {
|
||||
{navigate ? (
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
) : (
|
||||
@@ -498,14 +498,14 @@ describe('ReactInteractionTracing', () => {
|
||||
<>
|
||||
{showText ? (
|
||||
<Suspense
|
||||
name="show text"
|
||||
unstable_name="show text"
|
||||
fallback={<Text text="Show Text Loading..." />}>
|
||||
<AsyncText text="Show Text" />
|
||||
</Suspense>
|
||||
) : null}
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
</>
|
||||
@@ -605,14 +605,14 @@ describe('ReactInteractionTracing', () => {
|
||||
<>
|
||||
{showText ? (
|
||||
<Suspense
|
||||
name="show text"
|
||||
unstable_name="show text"
|
||||
fallback={<Text text="Show Text Loading..." />}>
|
||||
<AsyncText text="Show Text" />
|
||||
</Suspense>
|
||||
) : null}
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
</>
|
||||
@@ -719,16 +719,16 @@ describe('ReactInteractionTracing', () => {
|
||||
<>
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
<Suspense
|
||||
name="show text one"
|
||||
unstable_name="show text one"
|
||||
fallback={<Text text="Show Text One Loading..." />}>
|
||||
<AsyncText text="Show Text One" />
|
||||
</Suspense>
|
||||
<div>
|
||||
<Suspense
|
||||
name="show text two"
|
||||
unstable_name="show text two"
|
||||
fallback={<Text text="Show Text Two Loading..." />}>
|
||||
<AsyncText text="Show Text Two" />
|
||||
</Suspense>
|
||||
@@ -848,12 +848,12 @@ describe('ReactInteractionTracing', () => {
|
||||
<>
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
{/* showTextOne is entangled with navigate */}
|
||||
{showTextOne ? (
|
||||
<Suspense
|
||||
name="show text one"
|
||||
unstable_name="show text one"
|
||||
fallback={<Text text="Show Text One Loading..." />}>
|
||||
<AsyncText text="Show Text One" />
|
||||
</Suspense>
|
||||
@@ -865,7 +865,7 @@ describe('ReactInteractionTracing', () => {
|
||||
from completing */}
|
||||
{showTextTwo ? (
|
||||
<Suspense
|
||||
name="show text two"
|
||||
unstable_name="show text two"
|
||||
fallback={<Text text="Show Text Two Loading..." />}>
|
||||
<AsyncText text="Show Text Two" />
|
||||
</Suspense>
|
||||
@@ -1115,13 +1115,13 @@ describe('ReactInteractionTracing', () => {
|
||||
{navigate ? (
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="suspense page">
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
<React.unstable_TracingMarker name="sync marker" />
|
||||
<React.unstable_TracingMarker name="async marker">
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
name="marker suspense">
|
||||
unstable_name="marker suspense">
|
||||
<AsyncText text="Marker Text" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
@@ -1226,18 +1226,20 @@ describe('ReactInteractionTracing', () => {
|
||||
<div>
|
||||
{navigate ? (
|
||||
<React.unstable_TracingMarker name="outer marker">
|
||||
<Suspense fallback={<Text text="Outer..." />} name="outer">
|
||||
<Suspense
|
||||
fallback={<Text text="Outer..." />}
|
||||
unstable_name="outer">
|
||||
<AsyncText text="Outer Text" />
|
||||
<Suspense
|
||||
fallback={<Text text="Inner One..." />}
|
||||
name="inner one">
|
||||
unstable_name="inner one">
|
||||
<React.unstable_TracingMarker name="marker one">
|
||||
<AsyncText text="Inner Text One" />
|
||||
</React.unstable_TracingMarker>
|
||||
</Suspense>
|
||||
<Suspense
|
||||
fallback={<Text text="Inner Two..." />}
|
||||
name="inner two">
|
||||
unstable_name="inner two">
|
||||
<React.unstable_TracingMarker name="marker two">
|
||||
<AsyncText text="Inner Text Two" />
|
||||
</React.unstable_TracingMarker>
|
||||
@@ -1486,21 +1488,21 @@ describe('ReactInteractionTracing', () => {
|
||||
{showMarker ? (
|
||||
<React.unstable_TracingMarker name="marker one">
|
||||
<Suspense
|
||||
name="suspense page"
|
||||
unstable_name="suspense page"
|
||||
fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
) : (
|
||||
<Suspense
|
||||
name="suspense page"
|
||||
unstable_name="suspense page"
|
||||
fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
)}
|
||||
<React.unstable_TracingMarker name="sibling">
|
||||
<Suspense
|
||||
name="suspense sibling"
|
||||
unstable_name="suspense sibling"
|
||||
fallback={<Text text="Sibling Loading..." />}>
|
||||
<AsyncText text="Sibling Text" />
|
||||
</Suspense>
|
||||
@@ -1650,7 +1652,7 @@ describe('ReactInteractionTracing', () => {
|
||||
<div>
|
||||
<React.unstable_TracingMarker name="one">
|
||||
<Suspense
|
||||
name="suspense one"
|
||||
unstable_name="suspense one"
|
||||
fallback={<Text text="Loading One..." />}>
|
||||
<AsyncText text="Page One" />
|
||||
</Suspense>
|
||||
@@ -1659,7 +1661,7 @@ describe('ReactInteractionTracing', () => {
|
||||
) : null}
|
||||
<React.unstable_TracingMarker name="two">
|
||||
<Suspense
|
||||
name="suspense two"
|
||||
unstable_name="suspense two"
|
||||
fallback={<Text text="Loading Two..." />}>
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
@@ -1786,12 +1788,12 @@ describe('ReactInteractionTracing', () => {
|
||||
<React.unstable_TracingMarker name="one">
|
||||
{!deleteOne ? (
|
||||
<Suspense
|
||||
name="suspense one"
|
||||
unstable_name="suspense one"
|
||||
fallback={<Text text="Loading One..." />}>
|
||||
<AsyncText text="Page One" />
|
||||
<React.unstable_TracingMarker name="page one" />
|
||||
<Suspense
|
||||
name="suspense child"
|
||||
unstable_name="suspense child"
|
||||
fallback={<Text text="Loading Child..." />}>
|
||||
<React.unstable_TracingMarker name="child" />
|
||||
<AsyncText text="Child" />
|
||||
@@ -1801,7 +1803,7 @@ describe('ReactInteractionTracing', () => {
|
||||
</React.unstable_TracingMarker>
|
||||
<React.unstable_TracingMarker name="two">
|
||||
<Suspense
|
||||
name="suspense two"
|
||||
unstable_name="suspense two"
|
||||
fallback={<Text text="Loading Two..." />}>
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
@@ -1946,11 +1948,11 @@ describe('ReactInteractionTracing', () => {
|
||||
return (
|
||||
<React.unstable_TracingMarker name="parent">
|
||||
{show ? (
|
||||
<Suspense name="appended child">
|
||||
<Suspense unstable_name="appended child">
|
||||
<AsyncText text="Appended child" />
|
||||
</Suspense>
|
||||
) : null}
|
||||
<Suspense name="child">
|
||||
<Suspense unstable_name="child">
|
||||
<AsyncText text="Child" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
@@ -2066,13 +2068,13 @@ describe('ReactInteractionTracing', () => {
|
||||
{show ? (
|
||||
<React.unstable_TracingMarker name="appended child">
|
||||
{showSuspense ? (
|
||||
<Suspense name="appended child">
|
||||
<Suspense unstable_name="appended child">
|
||||
<AsyncText text="Appended child" />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</React.unstable_TracingMarker>
|
||||
) : null}
|
||||
<Suspense name="child">
|
||||
<Suspense unstable_name="child">
|
||||
<AsyncText text="Child" />
|
||||
</Suspense>
|
||||
</React.unstable_TracingMarker>
|
||||
@@ -2347,7 +2349,9 @@ describe('ReactInteractionTracing', () => {
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<Text text="Loading..." />} name="suspense page">
|
||||
<Suspense
|
||||
fallback={<Text text="Loading..." />}
|
||||
unstable_name="suspense page">
|
||||
<AsyncText text="Page Two" />
|
||||
</Suspense>
|
||||
);
|
||||
@@ -2412,10 +2416,12 @@ describe('ReactInteractionTracing', () => {
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Suspense name="one" fallback={<Text text="Loading..." />}>
|
||||
<Suspense unstable_name="one" fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="Text" />
|
||||
</Suspense>
|
||||
<Suspense name="two" fallback={<Text text="Loading Two..." />}>
|
||||
<Suspense
|
||||
unstable_name="two"
|
||||
fallback={<Text text="Loading Two..." />}>
|
||||
<AsyncText text="Text Two" />
|
||||
</Suspense>
|
||||
</>
|
||||
@@ -2484,7 +2490,9 @@ describe('ReactInteractionTracing', () => {
|
||||
function App({name}) {
|
||||
return (
|
||||
<>
|
||||
<Suspense name={name} fallback={<Text text={`Loading ${name}...`} />}>
|
||||
<Suspense
|
||||
unstable_name={name}
|
||||
fallback={<Text text={`Loading ${name}...`} />}>
|
||||
<AsyncText text={`Text ${name}`} />
|
||||
</Suspense>
|
||||
</>
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('ReactDOMServerFB', () => {
|
||||
});
|
||||
const result = readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script id="«R»">INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -193,10 +193,7 @@ describe('ReactFlightDOM', () => {
|
||||
node.tagName !== 'TEMPLATE' &&
|
||||
node.tagName !== 'template' &&
|
||||
!node.hasAttribute('hidden') &&
|
||||
!node.hasAttribute('aria-hidden') &&
|
||||
// Ignore the render blocking expect
|
||||
(node.getAttribute('rel') !== 'expect' ||
|
||||
node.getAttribute('blocking') !== 'render'))
|
||||
!node.hasAttribute('aria-hidden'))
|
||||
) {
|
||||
const props = {};
|
||||
const attributes = node.attributes;
|
||||
@@ -1920,15 +1917,11 @@ describe('ReactFlightDOM', () => {
|
||||
|
||||
expect(content1).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="preload" href="before1" as="style"/>' +
|
||||
'<link rel="preload" href="after1" as="style"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><p>hello world</p><template id="«R»"></template></body></html>',
|
||||
'<link rel="preload" href="after1" as="style"/></head><body><p>hello world</p></body></html>',
|
||||
);
|
||||
expect(content2).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="preload" href="before2" as="style"/>' +
|
||||
'<link rel="preload" href="after2" as="style"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><p>hello world</p><template id="«R»"></template></body></html>',
|
||||
'<link rel="preload" href="after2" as="style"/></head><body><p>hello world</p></body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1899,8 +1899,8 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
}
|
||||
|
||||
expect(content).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><p>hello world</p><template id="«R»"></template></body></html>',
|
||||
'<!DOCTYPE html><html><head>' +
|
||||
'</head><body><p>hello world</p></body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
6
packages/react-server/src/ReactFizzServer.js
vendored
6
packages/react-server/src/ReactFizzServer.js
vendored
@@ -5157,11 +5157,7 @@ function flushCompletedQueues(
|
||||
);
|
||||
flushSegment(request, destination, completedRootSegment, null);
|
||||
request.completedRootSegment = null;
|
||||
writeCompletedRoot(
|
||||
destination,
|
||||
request.resumableState,
|
||||
request.renderState,
|
||||
);
|
||||
writeCompletedRoot(destination, request.renderState);
|
||||
}
|
||||
|
||||
writeHoistables(destination, request.resumableState, request.renderState);
|
||||
|
||||
@@ -278,7 +278,7 @@ export type SuspenseProps = {
|
||||
|
||||
unstable_avoidThisFallback?: boolean,
|
||||
unstable_expectedLoadTime?: number,
|
||||
name?: string,
|
||||
unstable_name?: string,
|
||||
};
|
||||
|
||||
export type TracingMarkerProps = {
|
||||
|
||||
@@ -393,8 +393,7 @@ function getPlugins(
|
||||
};
|
||||
},
|
||||
},
|
||||
// See https://github.com/rollup/plugins/issues/1425
|
||||
bundle.tsconfig != null ? commonjs({strictRequires: true}) : false,
|
||||
bundle.tsconfig != null ? commonjs() : false,
|
||||
// Shim any modules that need forking in this environment.
|
||||
useForks(forks),
|
||||
// Ensure we don't try to bundle any fbjs modules.
|
||||
|
||||
Reference in New Issue
Block a user