mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Add entry points for "static" server rendering passes (#24752)
This will be used to add optimizations for static server rendering.
This commit is contained in:
parent
f796fa13ad
commit
0f216ae31d
7
packages/react-dom/npm/static.browser.js
Normal file
7
packages/react-dom/npm/static.browser.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-dom-static.browser.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-dom-static.browser.development.js');
|
||||
}
|
||||
3
packages/react-dom/npm/static.js
vendored
Normal file
3
packages/react-dom/npm/static.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./static.node');
|
||||
7
packages/react-dom/npm/static.node.js
Normal file
7
packages/react-dom/npm/static.node.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-dom-static.node.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-dom-static.node.development.js');
|
||||
}
|
||||
|
|
@ -32,6 +32,9 @@
|
|||
"server.js",
|
||||
"server.browser.js",
|
||||
"server.node.js",
|
||||
"static.js",
|
||||
"static.browser.js",
|
||||
"static.node.js",
|
||||
"test-utils.js",
|
||||
"unstable_testing.js",
|
||||
"cjs/",
|
||||
|
|
@ -48,6 +51,14 @@
|
|||
},
|
||||
"./server.browser": "./server.browser.js",
|
||||
"./server.node": "./server.node.js",
|
||||
"./static": {
|
||||
"deno": "./static.browser.js",
|
||||
"worker": "./static.browser.js",
|
||||
"browser": "./static.browser.js",
|
||||
"default": "./static.node.js"
|
||||
},
|
||||
"./static.browser": "./static.browser.js",
|
||||
"./static.node": "./static.node.js",
|
||||
"./profiling": "./profiling.js",
|
||||
"./test-utils": "./test-utils.js",
|
||||
"./unstable_testing": "./unstable_testing.js",
|
||||
|
|
@ -55,7 +66,8 @@
|
|||
"./package.json": "./package.json"
|
||||
},
|
||||
"browser": {
|
||||
"./server.js": "./server.browser.js"
|
||||
"./server.js": "./server.browser.js",
|
||||
"./static.js": "./static.browser.js"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
|
|
|||
237
packages/react-dom/src/__tests__/ReactDOMFizzStatic-test.js
vendored
Normal file
237
packages/react-dom/src/__tests__/ReactDOMFizzStatic-test.js
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let JSDOM;
|
||||
let Stream;
|
||||
let React;
|
||||
let ReactDOMClient;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
let textCache;
|
||||
let document;
|
||||
let writable;
|
||||
let container;
|
||||
let buffer = '';
|
||||
let hasErrored = false;
|
||||
let fatalError = undefined;
|
||||
|
||||
describe('ReactDOMFizzStatic', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
JSDOM = require('jsdom').JSDOM;
|
||||
React = require('react');
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
if (__EXPERIMENTAL__) {
|
||||
ReactDOMFizzStatic = require('react-dom/static');
|
||||
}
|
||||
Stream = require('stream');
|
||||
Suspense = React.Suspense;
|
||||
|
||||
textCache = new Map();
|
||||
|
||||
// Test Environment
|
||||
const jsdom = new JSDOM(
|
||||
'<!DOCTYPE html><html><head></head><body><div id="container">',
|
||||
{
|
||||
runScripts: 'dangerously',
|
||||
},
|
||||
);
|
||||
document = jsdom.window.document;
|
||||
container = document.getElementById('container');
|
||||
|
||||
buffer = '';
|
||||
hasErrored = false;
|
||||
|
||||
writable = new Stream.PassThrough();
|
||||
writable.setEncoding('utf8');
|
||||
writable.on('data', chunk => {
|
||||
buffer += chunk;
|
||||
});
|
||||
writable.on('error', error => {
|
||||
hasErrored = true;
|
||||
fatalError = error;
|
||||
});
|
||||
});
|
||||
|
||||
async function act(callback) {
|
||||
await callback();
|
||||
// Await one turn around the event loop.
|
||||
// This assumes that we'll flush everything we have so far.
|
||||
await new Promise(resolve => {
|
||||
setImmediate(resolve);
|
||||
});
|
||||
if (hasErrored) {
|
||||
throw fatalError;
|
||||
}
|
||||
// JSDOM doesn't support stream HTML parser so we need to give it a proper fragment.
|
||||
// We also want to execute any scripts that are embedded.
|
||||
// We assume that we have now received a proper fragment of HTML.
|
||||
const bufferedContent = buffer;
|
||||
buffer = '';
|
||||
const fakeBody = document.createElement('body');
|
||||
fakeBody.innerHTML = bufferedContent;
|
||||
while (fakeBody.firstChild) {
|
||||
const node = fakeBody.firstChild;
|
||||
if (node.nodeName === 'SCRIPT') {
|
||||
const script = document.createElement('script');
|
||||
script.textContent = node.textContent;
|
||||
fakeBody.removeChild(node);
|
||||
container.appendChild(script);
|
||||
} else {
|
||||
container.appendChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getVisibleChildren(element) {
|
||||
const children = [];
|
||||
let node = element.firstChild;
|
||||
while (node) {
|
||||
if (node.nodeType === 1) {
|
||||
if (
|
||||
node.tagName !== 'SCRIPT' &&
|
||||
node.tagName !== 'TEMPLATE' &&
|
||||
node.tagName !== 'template' &&
|
||||
!node.hasAttribute('hidden') &&
|
||||
!node.hasAttribute('aria-hidden')
|
||||
) {
|
||||
const props = {};
|
||||
const attributes = node.attributes;
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
if (
|
||||
attributes[i].name === 'id' &&
|
||||
attributes[i].value.includes(':')
|
||||
) {
|
||||
// We assume this is a React added ID that's a non-visual implementation detail.
|
||||
continue;
|
||||
}
|
||||
props[attributes[i].name] = attributes[i].value;
|
||||
}
|
||||
props.children = getVisibleChildren(node);
|
||||
children.push(React.createElement(node.tagName.toLowerCase(), props));
|
||||
}
|
||||
} else if (node.nodeType === 3) {
|
||||
children.push(node.data);
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return children.length === 0
|
||||
? undefined
|
||||
: children.length === 1
|
||||
? children[0]
|
||||
: children;
|
||||
}
|
||||
|
||||
function resolveText(text) {
|
||||
const record = textCache.get(text);
|
||||
if (record === undefined) {
|
||||
const newRecord = {
|
||||
status: 'resolved',
|
||||
value: text,
|
||||
};
|
||||
textCache.set(text, newRecord);
|
||||
} else if (record.status === 'pending') {
|
||||
const thenable = record.value;
|
||||
record.status = 'resolved';
|
||||
record.value = text;
|
||||
thenable.pings.forEach(t => t());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function rejectText(text, error) {
|
||||
const record = textCache.get(text);
|
||||
if (record === undefined) {
|
||||
const newRecord = {
|
||||
status: 'rejected',
|
||||
value: error,
|
||||
};
|
||||
textCache.set(text, newRecord);
|
||||
} else if (record.status === 'pending') {
|
||||
const thenable = record.value;
|
||||
record.status = 'rejected';
|
||||
record.value = error;
|
||||
thenable.pings.forEach(t => t());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
function readText(text) {
|
||||
const record = textCache.get(text);
|
||||
if (record !== undefined) {
|
||||
switch (record.status) {
|
||||
case 'pending':
|
||||
throw record.value;
|
||||
case 'rejected':
|
||||
throw record.value;
|
||||
case 'resolved':
|
||||
return record.value;
|
||||
}
|
||||
} else {
|
||||
const thenable = {
|
||||
pings: [],
|
||||
then(resolve) {
|
||||
if (newRecord.status === 'pending') {
|
||||
thenable.pings.push(resolve);
|
||||
} else {
|
||||
Promise.resolve().then(() => resolve(newRecord.value));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const newRecord = {
|
||||
status: 'pending',
|
||||
value: thenable,
|
||||
};
|
||||
textCache.set(text, newRecord);
|
||||
|
||||
throw thenable;
|
||||
}
|
||||
}
|
||||
|
||||
function Text({text}) {
|
||||
return text;
|
||||
}
|
||||
|
||||
function AsyncText({text}) {
|
||||
return readText(text);
|
||||
}
|
||||
|
||||
// @gate experimental
|
||||
it('should render a fully static document, send it and then hydrate it', async () => {
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="Hello" />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const promise = ReactDOMFizzStatic.prerenderToNodeStreams(<App />);
|
||||
|
||||
resolveText('Hello');
|
||||
|
||||
const result = await promise;
|
||||
|
||||
await act(async () => {
|
||||
result.prelude.pipe(writable);
|
||||
});
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
|
||||
|
||||
await act(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
});
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
|
||||
});
|
||||
});
|
||||
412
packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
vendored
Normal file
412
packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
vendored
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
|
||||
let React;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
|
||||
describe('ReactDOMFizzStaticBrowser', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
if (__EXPERIMENTAL__) {
|
||||
ReactDOMFizzStatic = require('react-dom/static.browser');
|
||||
}
|
||||
Suspense = React.Suspense;
|
||||
});
|
||||
|
||||
const theError = new Error('This is an error');
|
||||
function Throw() {
|
||||
throw theError;
|
||||
}
|
||||
const theInfinitePromise = new Promise(() => {});
|
||||
function InfiniteSuspend() {
|
||||
throw theInfinitePromise;
|
||||
}
|
||||
|
||||
async function readContent(stream) {
|
||||
const reader = stream.getReader();
|
||||
let content = '';
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
if (done) {
|
||||
return content;
|
||||
}
|
||||
content += Buffer.from(value).toString('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
// @gate experimental
|
||||
it('should call prerender', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerender(<div>hello world</div>);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(`"<div>hello world</div>"`);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should emit DOCTYPE at the root of the document', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerender(
|
||||
<html>
|
||||
<body>hello world</body>
|
||||
</html>,
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><body>hello world</body></html>"`,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerender(<div>hello world</div>, {
|
||||
bootstrapScriptContent: 'INIT();',
|
||||
bootstrapScripts: ['init.js'],
|
||||
bootstrapModules: ['init.mjs'],
|
||||
});
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<div>hello world</div><script>INIT();</script><script src=\\"init.js\\" async=\\"\\"></script><script type=\\"module\\" src=\\"init.mjs\\" async=\\"\\"></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('emits all HTML as one unit', async () => {
|
||||
let hasLoaded = false;
|
||||
let resolve;
|
||||
const promise = new Promise(r => (resolve = r));
|
||||
function Wait() {
|
||||
if (!hasLoaded) {
|
||||
throw promise;
|
||||
}
|
||||
return 'Done';
|
||||
}
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback="Loading">
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Resolve the loading.
|
||||
hasLoaded = true;
|
||||
await resolve();
|
||||
|
||||
const result = await resultPromise;
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<div><!--$-->Done<!-- --><!--/$--></div>"`,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject the promise when an error is thrown at the root', async () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Throw />
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theError);
|
||||
expect(reportedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject the promise when an error is thrown inside a fallback', async () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<Throw />}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theError);
|
||||
expect(reportedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
|
||||
const reportedErrors = [];
|
||||
const result = await ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Throw />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toContain('Loading');
|
||||
expect(reportedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should be able to complete by aborting even if the promise never resolves', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
controller.abort();
|
||||
|
||||
const result = await resultPromise;
|
||||
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toContain('Loading');
|
||||
|
||||
expect(errors).toEqual([
|
||||
'The render was aborted by the server without a reason.',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject if aborting before the shell is complete', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const promise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
const theReason = new Error('aborted for reasons');
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = theReason;
|
||||
controller.abort(theReason);
|
||||
|
||||
let caughtError = null;
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theReason);
|
||||
expect(errors).toEqual(['aborted for reasons']);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should be able to abort before something suspends', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
function App() {
|
||||
controller.abort();
|
||||
return (
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
const streamPromise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let caughtError = null;
|
||||
try {
|
||||
await streamPromise;
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError.message).toBe(
|
||||
'The render was aborted by the server without a reason.',
|
||||
);
|
||||
expect(errors).toEqual([
|
||||
'The render was aborted by the server without a reason.',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject if passing an already aborted signal', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const theReason = new Error('aborted for reasons');
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = theReason;
|
||||
controller.abort(theReason);
|
||||
|
||||
const promise = ReactDOMFizzStatic.prerender(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Technically we could still continue rendering the shell but currently the
|
||||
// semantics mean that we also abort any pending CPU work.
|
||||
let caughtError = null;
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theReason);
|
||||
expect(errors).toEqual(['aborted for reasons']);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('supports custom abort reasons with a string', async () => {
|
||||
const promise = new Promise(r => {});
|
||||
function Wait() {
|
||||
throw promise;
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Suspense fallback={'p'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</p>
|
||||
<span>
|
||||
<Suspense fallback={'span'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = 'foobar';
|
||||
controller.abort('foobar');
|
||||
|
||||
await resultPromise;
|
||||
|
||||
expect(errors).toEqual(['foobar', 'foobar']);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('supports custom abort reasons with an Error', async () => {
|
||||
const promise = new Promise(r => {});
|
||||
function Wait() {
|
||||
throw promise;
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Suspense fallback={'p'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</p>
|
||||
<span>
|
||||
<Suspense fallback={'span'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerender(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = new Error('uh oh');
|
||||
controller.abort(new Error('uh oh'));
|
||||
|
||||
await resultPromise;
|
||||
|
||||
expect(errors).toEqual(['uh oh', 'uh oh']);
|
||||
});
|
||||
});
|
||||
421
packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js
vendored
Normal file
421
packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js
vendored
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
// TODO: This should actually run in `@jest-environment node` but we currently
|
||||
// run an old jest that doesn't support AbortController so we use DOM for now.
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
|
||||
describe('ReactDOMFizzStaticNode', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
if (__EXPERIMENTAL__) {
|
||||
ReactDOMFizzStatic = require('react-dom/static');
|
||||
}
|
||||
Suspense = React.Suspense;
|
||||
});
|
||||
|
||||
const theError = new Error('This is an error');
|
||||
function Throw() {
|
||||
throw theError;
|
||||
}
|
||||
const theInfinitePromise = new Promise(() => {});
|
||||
function InfiniteSuspend() {
|
||||
throw theInfinitePromise;
|
||||
}
|
||||
|
||||
function readContent(readable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let content = '';
|
||||
readable.on('data', chunk => {
|
||||
content += Buffer.from(chunk).toString('utf8');
|
||||
});
|
||||
readable.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
readable.on('end', () => resolve(content));
|
||||
});
|
||||
}
|
||||
|
||||
// @gate experimental
|
||||
it('should call prerenderToNodeStreams', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>hello world</div>,
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(`"<div>hello world</div>"`);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should emit DOCTYPE at the root of the document', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<html>
|
||||
<body>hello world</body>
|
||||
</html>,
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><body>hello world</body></html>"`,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>hello world</div>,
|
||||
{
|
||||
bootstrapScriptContent: 'INIT();',
|
||||
bootstrapScripts: ['init.js'],
|
||||
bootstrapModules: ['init.mjs'],
|
||||
},
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<div>hello world</div><script>INIT();</script><script src=\\"init.js\\" async=\\"\\"></script><script type=\\"module\\" src=\\"init.mjs\\" async=\\"\\"></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('emits all HTML as one unit', async () => {
|
||||
let hasLoaded = false;
|
||||
let resolve;
|
||||
const promise = new Promise(r => (resolve = r));
|
||||
function Wait() {
|
||||
if (!hasLoaded) {
|
||||
throw promise;
|
||||
}
|
||||
return 'Done';
|
||||
}
|
||||
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<Suspense fallback="Loading">
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Resolve the loading.
|
||||
hasLoaded = true;
|
||||
await resolve();
|
||||
|
||||
const result = await resultPromise;
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<div><!--$-->Done<!-- --><!--/$--></div>"`,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject the promise when an error is thrown at the root', async () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<Throw />
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theError);
|
||||
expect(reportedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject the promise when an error is thrown inside a fallback', async () => {
|
||||
const reportedErrors = [];
|
||||
let caughtError = null;
|
||||
try {
|
||||
await ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<Suspense fallback={<Throw />}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theError);
|
||||
expect(reportedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
|
||||
const reportedErrors = [];
|
||||
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Throw />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
onError(x) {
|
||||
reportedErrors.push(x);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toContain('Loading');
|
||||
expect(reportedErrors).toEqual([theError]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should be able to complete by aborting even if the promise never resolves', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
controller.abort();
|
||||
|
||||
const result = await resultPromise;
|
||||
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toContain('Loading');
|
||||
|
||||
expect(errors).toEqual([
|
||||
'The render was aborted by the server without a reason.',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject if aborting before the shell is complete', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const promise = ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<InfiniteSuspend />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
const theReason = new Error('aborted for reasons');
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = theReason;
|
||||
controller.abort(theReason);
|
||||
|
||||
let caughtError = null;
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theReason);
|
||||
expect(errors).toEqual(['aborted for reasons']);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should be able to abort before something suspends', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
function App() {
|
||||
controller.abort();
|
||||
return (
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
const streamPromise = ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<App />
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let caughtError = null;
|
||||
try {
|
||||
await streamPromise;
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError.message).toBe(
|
||||
'The render was aborted by the server without a reason.',
|
||||
);
|
||||
expect(errors).toEqual([
|
||||
'The render was aborted by the server without a reason.',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('should reject if passing an already aborted signal', async () => {
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const theReason = new Error('aborted for reasons');
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = theReason;
|
||||
controller.abort(theReason);
|
||||
|
||||
const promise = ReactDOMFizzStatic.prerenderToNodeStreams(
|
||||
<div>
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<InfiniteSuspend />
|
||||
</Suspense>
|
||||
</div>,
|
||||
{
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Technically we could still continue rendering the shell but currently the
|
||||
// semantics mean that we also abort any pending CPU work.
|
||||
let caughtError = null;
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
expect(caughtError).toBe(theReason);
|
||||
expect(errors).toEqual(['aborted for reasons']);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('supports custom abort reasons with a string', async () => {
|
||||
const promise = new Promise(r => {});
|
||||
function Wait() {
|
||||
throw promise;
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Suspense fallback={'p'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</p>
|
||||
<span>
|
||||
<Suspense fallback={'span'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = 'foobar';
|
||||
controller.abort('foobar');
|
||||
|
||||
await resultPromise;
|
||||
|
||||
expect(errors).toEqual(['foobar', 'foobar']);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('supports custom abort reasons with an Error', async () => {
|
||||
const promise = new Promise(r => {});
|
||||
function Wait() {
|
||||
throw promise;
|
||||
}
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Suspense fallback={'p'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</p>
|
||||
<span>
|
||||
<Suspense fallback={'span'}>
|
||||
<Wait />
|
||||
</Suspense>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
const controller = new AbortController();
|
||||
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(<App />, {
|
||||
signal: controller.signal,
|
||||
onError(x) {
|
||||
errors.push(x.message);
|
||||
return 'a digest';
|
||||
},
|
||||
});
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// @TODO this is a hack to work around lack of support for abortSignal.reason in node
|
||||
// The abort call itself should set this property but since we are testing in node we
|
||||
// set it here manually
|
||||
controller.signal.reason = new Error('uh oh');
|
||||
controller.abort(new Error('uh oh'));
|
||||
|
||||
await resultPromise;
|
||||
|
||||
expect(errors).toEqual(['uh oh', 'uh oh']);
|
||||
});
|
||||
});
|
||||
98
packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
vendored
Normal file
98
packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
startFlowing,
|
||||
abort,
|
||||
} from 'react-server/src/ReactFizzServer';
|
||||
|
||||
import {
|
||||
createResponseState,
|
||||
createRootFormatContext,
|
||||
} from './ReactDOMServerFormatConfig';
|
||||
|
||||
type Options = {|
|
||||
identifierPrefix?: string,
|
||||
namespaceURI?: string,
|
||||
bootstrapScriptContent?: string,
|
||||
bootstrapScripts?: Array<string>,
|
||||
bootstrapModules?: Array<string>,
|
||||
progressiveChunkSize?: number,
|
||||
signal?: AbortSignal,
|
||||
onError?: (error: mixed) => ?string,
|
||||
|};
|
||||
|
||||
type StaticResult = {|
|
||||
prelude: ReadableStream,
|
||||
|};
|
||||
|
||||
function prerender(
|
||||
children: ReactNodeList,
|
||||
options?: Options,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
|
||||
function onAllReady() {
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
pull(controller) {
|
||||
startFlowing(request, controller);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
|
||||
const result = {
|
||||
prelude: stream,
|
||||
};
|
||||
resolve(result);
|
||||
}
|
||||
const request = createRequest(
|
||||
children,
|
||||
createResponseState(
|
||||
options ? options.identifierPrefix : undefined,
|
||||
undefined,
|
||||
options ? options.bootstrapScriptContent : undefined,
|
||||
options ? options.bootstrapScripts : undefined,
|
||||
options ? options.bootstrapModules : undefined,
|
||||
),
|
||||
createRootFormatContext(options ? options.namespaceURI : undefined),
|
||||
options ? options.progressiveChunkSize : undefined,
|
||||
options ? options.onError : undefined,
|
||||
onAllReady,
|
||||
undefined,
|
||||
undefined,
|
||||
onFatalError,
|
||||
);
|
||||
if (options && options.signal) {
|
||||
const signal = options.signal;
|
||||
if (signal.aborted) {
|
||||
abort(request, (signal: any).reason);
|
||||
} else {
|
||||
const listener = () => {
|
||||
abort(request, (signal: any).reason);
|
||||
signal.removeEventListener('abort', listener);
|
||||
};
|
||||
signal.addEventListener('abort', listener);
|
||||
}
|
||||
}
|
||||
startWork(request);
|
||||
});
|
||||
}
|
||||
|
||||
export {prerender, ReactVersion as version};
|
||||
112
packages/react-dom/src/server/ReactDOMFizzStaticNode.js
vendored
Normal file
112
packages/react-dom/src/server/ReactDOMFizzStaticNode.js
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import {Writable, Readable} from 'stream';
|
||||
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
startFlowing,
|
||||
abort,
|
||||
} from 'react-server/src/ReactFizzServer';
|
||||
|
||||
import {
|
||||
createResponseState,
|
||||
createRootFormatContext,
|
||||
} from './ReactDOMServerFormatConfig';
|
||||
|
||||
type Options = {|
|
||||
identifierPrefix?: string,
|
||||
namespaceURI?: string,
|
||||
bootstrapScriptContent?: string,
|
||||
bootstrapScripts?: Array<string>,
|
||||
bootstrapModules?: Array<string>,
|
||||
progressiveChunkSize?: number,
|
||||
signal?: AbortSignal,
|
||||
onError?: (error: mixed) => ?string,
|
||||
|};
|
||||
|
||||
type StaticResult = {|
|
||||
prelude: Readable,
|
||||
|};
|
||||
|
||||
function createFakeWritable(readable): Writable {
|
||||
// The current host config expects a Writable so we create
|
||||
// a fake writable for now to push into the Readable.
|
||||
return ({
|
||||
write(chunk) {
|
||||
return readable.push(chunk);
|
||||
},
|
||||
end() {
|
||||
readable.push(null);
|
||||
},
|
||||
destroy(error) {
|
||||
readable.destroy(error);
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
function prerenderToNodeStreams(
|
||||
children: ReactNodeList,
|
||||
options?: Options,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
|
||||
function onAllReady() {
|
||||
const readable = new Readable({
|
||||
read() {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
});
|
||||
const writable = createFakeWritable(readable);
|
||||
|
||||
const result = {
|
||||
prelude: readable,
|
||||
};
|
||||
resolve(result);
|
||||
}
|
||||
|
||||
const request = createRequest(
|
||||
children,
|
||||
createResponseState(
|
||||
options ? options.identifierPrefix : undefined,
|
||||
undefined,
|
||||
options ? options.bootstrapScriptContent : undefined,
|
||||
options ? options.bootstrapScripts : undefined,
|
||||
options ? options.bootstrapModules : undefined,
|
||||
),
|
||||
createRootFormatContext(options ? options.namespaceURI : undefined),
|
||||
options ? options.progressiveChunkSize : undefined,
|
||||
options ? options.onError : undefined,
|
||||
onAllReady,
|
||||
undefined,
|
||||
undefined,
|
||||
onFatalError,
|
||||
);
|
||||
if (options && options.signal) {
|
||||
const signal = options.signal;
|
||||
if (signal.aborted) {
|
||||
abort(request, (signal: any).reason);
|
||||
} else {
|
||||
const listener = () => {
|
||||
abort(request, (signal: any).reason);
|
||||
signal.removeEventListener('abort', listener);
|
||||
};
|
||||
signal.addEventListener('abort', listener);
|
||||
}
|
||||
}
|
||||
startWork(request);
|
||||
});
|
||||
}
|
||||
|
||||
export {prerenderToNodeStreams, ReactVersion as version};
|
||||
10
packages/react-dom/static.browser.js
Normal file
10
packages/react-dom/static.browser.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export {prerender, version} from './src/server/ReactDOMFizzStaticBrowser';
|
||||
10
packages/react-dom/static.js
vendored
Normal file
10
packages/react-dom/static.js
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export * from './static.node';
|
||||
13
packages/react-dom/static.node.js
Normal file
13
packages/react-dom/static.node.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export {
|
||||
prerenderToNodeStreams,
|
||||
version,
|
||||
} from './src/server/ReactDOMFizzStaticNode';
|
||||
|
|
@ -330,6 +330,27 @@ const bundles = [
|
|||
externals: ['react'],
|
||||
},
|
||||
|
||||
/******* React DOM Fizz Static *******/
|
||||
{
|
||||
bundleTypes: __EXPERIMENTAL__ ? [NODE_DEV, NODE_PROD] : [],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-dom/static.browser',
|
||||
global: 'ReactDOMStatic',
|
||||
minifyWithProdErrorCodes: true,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react'],
|
||||
},
|
||||
{
|
||||
bundleTypes: __EXPERIMENTAL__ ? [NODE_DEV, NODE_PROD] : [],
|
||||
moduleType: RENDERER,
|
||||
entry: 'react-dom/static.node',
|
||||
name: 'react-dom-static.node',
|
||||
global: 'ReactDOMStatic',
|
||||
minifyWithProdErrorCodes: false,
|
||||
wrapWithModuleBoundaries: false,
|
||||
externals: ['react', 'util', 'stream'],
|
||||
},
|
||||
|
||||
/******* React Server DOM Webpack Writer *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const importSideEffects = Object.freeze({
|
|||
fs: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
'fs/promises': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
path: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
stream: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
'prop-types/checkPropTypes': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ function filterOutEntrypoints(name) {
|
|||
let packageJSON = JSON.parse(readFileSync(jsonPath));
|
||||
let files = packageJSON.files;
|
||||
let exportsJSON = packageJSON.exports;
|
||||
let browserJSON = packageJSON.browser;
|
||||
if (!Array.isArray(files)) {
|
||||
throw new Error('expected all package.json files to contain a files field');
|
||||
}
|
||||
|
|
@ -189,6 +190,9 @@ function filterOutEntrypoints(name) {
|
|||
delete exportsJSON['./' + filename.replace(/\.js$/, '')];
|
||||
}
|
||||
}
|
||||
if (browserJSON) {
|
||||
delete browserJSON['./' + filename];
|
||||
}
|
||||
}
|
||||
|
||||
// We only export the source directory so Jest and Rollup can access them
|
||||
|
|
|
|||
|
|
@ -13,13 +13,19 @@ module.exports = [
|
|||
'react-dom',
|
||||
'react-dom/unstable_testing',
|
||||
'react-dom/src/server/ReactDOMFizzServerNode.js',
|
||||
'react-dom/static.node',
|
||||
'react-server-dom-webpack/writer.node.server',
|
||||
'react-server-dom-webpack',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom/client',
|
||||
'react-dom/server',
|
||||
'react-dom/server.node',
|
||||
'react-dom/static',
|
||||
'react-dom/static.node',
|
||||
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
|
||||
'react-dom/src/server/ReactDOMFizzStaticNode.js',
|
||||
'react-server-dom-webpack',
|
||||
'react-server-dom-webpack/writer',
|
||||
'react-server-dom-webpack/writer.node.server',
|
||||
|
|
@ -40,14 +46,18 @@ module.exports = [
|
|||
'react-dom',
|
||||
'react-dom/unstable_testing',
|
||||
'react-dom/src/server/ReactDOMFizzServerBrowser.js',
|
||||
'react-dom/static.browser',
|
||||
'react-server-dom-webpack/writer.browser.server',
|
||||
'react-server-dom-webpack',
|
||||
],
|
||||
paths: [
|
||||
'react-dom',
|
||||
'react-dom/client',
|
||||
'react-dom/server.browser',
|
||||
'react-dom/static.browser',
|
||||
'react-dom/unstable_testing',
|
||||
'react-dom/src/server/ReactDOMFizzServerBrowser.js', // react-dom/server.browser
|
||||
'react-dom/src/server/ReactDOMFizzStaticBrowser.js',
|
||||
'react-server-dom-webpack',
|
||||
'react-server-dom-webpack/writer.browser.server',
|
||||
'react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js', // react-server-dom-webpack/writer.browser.server
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user