Skip to content

Commit

Permalink
Merge pull request #1385 from remotion-dev/serialize-default-props
Browse files Browse the repository at this point in the history
  • Loading branch information
JonnyBurger committed Oct 10, 2022
2 parents 02647c8 + 076ead3 commit 50d8b22
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 21 deletions.
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

1 comment on commit 50d8b22

@vercel
Copy link

@vercel vercel bot commented on 50d8b22 Oct 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.