Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

give guidance for huge defaultProps payloads #1385

Merged
merged 3 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 48 additions & 12 deletions packages/bundler/src/renderEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ import {Homepage} from './homepage/homepage';

Internals.CSSUtils.injectCSS(Internals.CSSUtils.makeDefaultCSS(null, '#fff'));

const getCanSerializeDefaultProps = (object: unknown) => {
try {
const str = JSON.stringify(object);
// 256MB is the theoretical limit, making it throw if over 90% of that is reached.
return str.length < 256 * 1024 * 1024 * 0.9;
} catch (err) {
if ((err as Error).message.includes('Invalid string length')) {
return false;
}

throw err;
}
};

const GetVideo: React.FC<{state: BundleState}> = ({state}) => {
const video = Internals.useVideo();
const compositions = useContext(Internals.CompositionManager);
Expand Down Expand Up @@ -215,18 +229,40 @@ if (typeof window !== 'undefined') {
throw new Error('Unexpectedly did not have a CompositionManager');
}

return Internals.compositionsRef.current
.getCompositions()
.map((c): TCompMetadata => {
return {
defaultProps: c.defaultProps,
durationInFrames: c.durationInFrames,
fps: c.fps,
height: c.height,
id: c.id,
width: c.width,
};
});
const compositions = Internals.compositionsRef.current.getCompositions();

const canSerializeDefaultProps = getCanSerializeDefaultProps(compositions);
if (!canSerializeDefaultProps) {
console.warn(
'defaultProps are too big to serialize - trying to find the problematic composition...'
);
for (const comp of compositions) {
if (!getCanSerializeDefaultProps(comp)) {
throw new Error(
`defaultProps too big - could not serialize - the defaultProps of composition with ID ${comp.id} - the object that was passed to defaultProps was too big. Learn how to mitigate this error by visiting https://remotion.dev/docs/troubleshooting/serialize-defaultprops`
);
}
}

console.warn(
'Could not single out a problematic composition - The composition list as a whole is too big to serialize.'
);

throw new Error(
'defaultProps too big - Could not serialize - an object that was passed to defaultProps was too big. Learn how to mitigate this error by visiting https://remotion.dev/docs/troubleshooting/serialize-defaultprops'
);
}

return compositions.map((c): TCompMetadata => {
return {
defaultProps: c.defaultProps,
durationInFrames: c.durationInFrames,
fps: c.fps,
height: c.height,
id: c.id,
width: c.width,
};
});
};

window.siteVersion = '4';
Expand Down
60 changes: 53 additions & 7 deletions packages/docs/docs/composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,79 @@ This is the component to use to register a video to make it renderable and make

A composition represents the video you want to create, as a collection of clips (for example, several `<Sequence>`) that will play back to back to form your video.

```tsx twoslash title="src/Video.tsx"
const Component: React.FC = () => null;
// ---cut---

import { Composition } from "remotion";

export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
component={Component}
durationInFrames={300}
width={1080}
height={1080}
fps={30}
id="test-render"
defaultProps={{}}
/>
{/* Additional compositions can be rendered */}
</>
);
};
```

## API

A `<Composition />` should be placed within a fragment of the root component (which is registered using [`registerRoot()`](/docs/register-root)).

The component takes the following props:

- `id`: ID of the composition, as shown in the sidebar and also a unique identifier of the composition that you need to specify if you want to render it. The ID can only contain letters, numbers and `-`.
### `id`

ID of the composition, as shown in the sidebar and also a unique identifier of the composition that you need to specify if you want to render it. The ID can only contain letters, numbers and `-`.

### `fps`

At how many frames the composition should be rendered.

### `durationInFrames`

How many frames the composition should be long.

- `fps`: At how many frames the composition should be rendered.
### `height`

- `durationInFrames`: How many frames the composition should be long.
Height of the composition in pixels.

- `height`: Height of the composition in pixels.
### `width`

- `width`: Width of the composition in pixels.
Width of the composition in pixels.

- `component` **or** `lazyComponent`: Pass the component in directly **or** pass a function that returns a dynamic import. Passing neither or both of the props is an error.
### `component` **or** `lazyComponent`

Pass the component in directly **or** pass a function that returns a dynamic import. Passing neither or both of the props is an error.

:::note
If you use `lazyComponent`, Remotion will use React Suspense to load the component. Components will be compiled by Webpack as they are needed, which will reduce startup time of Remotion. If you use `lazyComponent`, you need to use a default export for your component. This is a restriction of React Suspense.
:::

- `defaultProps` _optional_: Give your component default props that will be shown in the preview. You can override these props during render using a CLI flag.
### `defaultProps`

_optional_

Give your component default props that will be shown in the preview. You can override these props during render using a CLI flag.

:::note
Type your components using the `React.FC<{}>` type and the `defaultProps` prop will be typesafe.
:::

:::note
Passing huge objects to `defaultProps` can be slow. [Learn how to avoid it.](/docs/troubleshooting/defaultprops-too-big)

:::

## Example using `component`

```tsx twoslash
Expand Down Expand Up @@ -127,3 +172,4 @@ export const Video = () => {
- [`<Sequence />`](/docs/sequence)
- [`<Still />`](/docs/still)
- [`<Folder />`](/docs/folder)
- [Avoid huge payloads for `defaultProps`](/docs/troubleshooting/defaultprops-too-big)
4 changes: 4 additions & 0 deletions packages/docs/docs/parametrized-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,7 @@ you can still use it as normal in your videos and pass it's props directly. Defa
// ---cut---
<MyComponent propOne="hi" propTwo={10} />
```

## See also

- [Avoid huge payloads for `defaultProps`](/docs/troubleshooting/defaultprops-too-big)
127 changes: 127 additions & 0 deletions packages/docs/docs/troubleshooting/defaultprops-too-big.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
id: defaultprops-too-big
sidebar_label: defaultProps too big
title: defaultProps too big - could not serialize
---

If you experience an error during rendering:

```
defaultProps too big - could not serialize - the defaultProps of composition with ID "[composition-id]" - the object that was passed to defaultProps was too big. Learn how to mitigate this error by visiting https://remotion.dev/docs/troubleshooting/serialize-defaultprops
```

It means the object that was passed to [`defaultProps`](/docs/composition#defaultprops) for the specified composition is too big that Chrome cannot serialize it into a string.

If you see this variant of the error:

```
defaultProps too big - Could not serialize - an object that was passed to defaultProps was too big. Learn how to mitigate this error by visiting https://remotion.dev/docs/troubleshooting/serialize-defaultprops
```

The [`defaultProps`](/docs/composition#defaultprops) is not too big, but the list of compositions as a whole is too big to serialize.

## Why this error occurs

Remotion is trying to gather a list of compositions using [`getCompositions()`](/docs/renderer/get-compositions), and is copying them from the headless browser into Node.JS. During this operation, the JavaScript object needs to be converted into a string. The upper limit for this is 256MB, however dependending on the machine Remotion runs on, the error may also occur on smaller payloads.

## How to fix the error

Avoid passing huge data payloads to `defaultProps`. Instead, derive the big data payload inside the component based on slim `defaultProps`. For example:

- Instead of passing a data URL or Buffer that represents an asset, pass a URL to the asset and fetch the asset from inside the composition
- Don't fetch an audio visualization using [`getAudioData()`](/docs/get-audio-data) and pass it as default props - instead, pass the URL of the audio asset and fetch the audio visualization inside the component.

Example:

```tsx twoslash title="❌ Avoid - big data chunk as defaultProps"
import { AudioData, getAudioData } from "@remotion/media-utils";
import { useEffect, useState } from "react";
import { Composition, continueRender, delayRender, staticFile } from "remotion";

// MyComp.tsx
const MyComp: React.FC<{
audioData: AudioData | null;
}> = ({ audioData }) => {
return null;
};

// src/Video.tsx
const RemotionRoot = () => {
const [audioData, setAudioData] = useState<AudioData | null>(null);
const [handle] = useState(() => delayRender());

useEffect(() => {
getAudioData(staticFile("audio.mp3"))
.then((data) => {
setAudioData(data);
continueRender(handle);
})
.catch((e) => {
console.log(e);
});
}, [handle]);

return (
<Composition
id="my-comp"
durationInFrames={90}
width={1080}
height={1080}
fps={1080}
component={MyComp}
defaultProps={{
audioData,
}}
/>
);
};
```

This can lead to problems because the `audioData` variable can become very big since it contains raw data about the waveform, and Remotion needs to serialize this data into a string, which is slow and can lead to this error. Instead, pass the audio URL and do the audio data fetching inside the component:

```tsx twoslash title="✅ Do - Fetch data inside composition"
import { getAudioData } from "@remotion/media-utils";
import { useEffect, useState } from "react";
import { Composition, continueRender, delayRender, staticFile } from "remotion";

// MyComp.tsx
const MyComp: React.FC<{ src: string }> = ({ src }) => {
const [audioData, setAudioData] = useState<any>(undefined);
const [handle] = useState(() => delayRender());

useEffect(() => {
getAudioData(src)
.then((data) => {
setAudioData(data);
continueRender(handle);
})
.catch((e) => {
console.log(e);
});
}, [handle]);

return null;
};

// src/Video.tsx
const RemotionRoot = () => {
return (
<Composition
id="my-comp"
durationInFrames={90}
width={1080}
height={1080}
fps={1080}
component={MyComp}
defaultProps={{
src: staticFile("audio.mp3"),
}}
/>
);
};
```

## See also

- [Parametrized rendering](/docs/parametrized-rendering)
- [`<Composition>`](/docs/composition)
2 changes: 1 addition & 1 deletion packages/docs/docs/troubleshooting/rosetta.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: rosetta
sidebar_label: Rosetta
sidebar_label: Apple Silicon detected
title: Apple Silicon under Rosetta
---

Expand Down
3 changes: 2 additions & 1 deletion packages/docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ module.exports = {
"wrong-composition-mount",
"staticfile-relative-paths",
"staticfile-remote-urls",
"troubleshooting/rosetta"
"troubleshooting/rosetta",
"troubleshooting/defaultprops-too-big",
],
},

Expand Down