Back to Remotion

Just-in-time compilation of Remotion code

packages/docs/docs/ai/dynamic-compilation.mdx

4.0.4686.9 KB
Original Source

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

If you have generated a Remotion component as a string (for example using LLMs), you can compile it in the browser to display a live preview.

tsx
const code = `
const MyComponent = () => {
  return <div style={{fontSize: 100}}>Hello</div>;
};
`;

To make this executable, first transpile it with @babel/standalone:

tsx
import * as Babel from '@babel/standalone';

const transpiled = Babel.transform(code, {
  presets: ['react', 'typescript'],
});

After transpilation, the result is this string:

js
'const MyComponent = () => { return React.createElement("div", {style: {fontSize: 100}}, "Hello"); };';

Use JavaScript's Function constructor to turn the string into a real function:

tsx
const createComponent = new Function('React', `${transpiled.code}\nreturn MyComponent;`);

This creates a function equivalent to:

tsx
function createComponent(React) {
  const MyComponent = () => { return React.createElement("div", ...); };

  return MyComponent;
}

Calling this function and passing the actual React library makes the component available.
The component code is wrapped inside, and dependencies (in this case just React) are passed in as arguments.

tsx
const Component = createComponent(React);

Adding Remotion APIs

Just React won't be enough for animations.
Remotion components need access to APIs like useCurrentFrame() and <AbsoluteFill>. These are injected the same way.

All APIs used within the code must be explicitly passed in.

Here's an example with a few Remotion APIs:

tsx
import {AbsoluteFill, useCurrentFrame, spring} from 'remotion';

const createComponent = new Function('React', 'AbsoluteFill', 'useCurrentFrame', 'spring', `${transpiled.code}\nreturn MyComponent;`);

const Component = createComponent(React, AbsoluteFill, useCurrentFrame, spring);

Each parameter name becomes an available variable inside the executed code.

Handling Import Statements

AI-generated code typically includes import statements.
Since APIs are injected manually via the Function constructor, these imports need to be stripped:

tsx
// AI generates this:
import {useCurrentFrame, AbsoluteFill} from 'remotion';

export const MyComposition = () => {
  const frame = useCurrentFrame();
  return <AbsoluteFill>...</AbsoluteFill>;
};

The imports are removed and just the component body is extracted:

tsx
// Step 1: Remove imports (these are injected manually)
const codeWithoutImports = code.replace(/^import\s+.*$/gm, '').trim();

// Step 2: Extract component body from "export const X = () => { ... };"
const match = codeWithoutImports.match(/export\s+const\s+\w+\s*=\s*\(\s*\)\s*=>\s*\{([\s\S]*)\};?\s*$/);
const componentBody = match ? match[1].trim() : codeWithoutImports;

// Step 3: Wrap it back into a component
const wrappedSource = `const DynamicComponent = () => {\n${componentBody}\n};`;

Complete Example

In this example, a React component is compiled and rendered by the <Player> component.

<Tabs defaultValue="preview"> <TabItem value="preview" label="Preview.tsx">
tsx
// @noErrors
import {Player} from '@remotion/player';
import {useCompilation} from './use-compilation';

export const Preview: React.FC<{code: string}> = ({code}) => {
  const {Component, error} = useCompilation(code);

  if (error) {
    return <div style={{color: 'red'}}>Error: {error}</div>;
  }

  if (!Component) {
    return null;
  }

  return <Player component={Component} durationInFrames={150} fps={30} compositionWidth={1920} compositionHeight={1080} controls />;
};
</TabItem> <TabItem value="hook" label="use-compilation.ts" default>
tsx
import * as Babel from '@babel/standalone';
import React, {useMemo} from 'react';
import {AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate, Sequence} from 'remotion';

interface CompilationResult {
  Component: React.ComponentType | null;
  error: string | null;
}

export function useCompilation(code: string): CompilationResult {
  return useMemo(() => {
    if (!code?.trim()) {
      return {Component: null, error: null};
    }

    try {
      // Strip imports and extract component body
      const codeWithoutImports = code.replace(/^import\s+.*$/gm, '').trim();
      const match = codeWithoutImports.match(/export\s+const\s+\w+\s*=\s*\(\s*\)\s*=>\s*\{([\s\S]*)\};?\s*$/);
      const componentBody = match ? match[1].trim() : codeWithoutImports;
      const wrappedSource = `const DynamicComponent = () => {\n${componentBody}\n};`;

      // Transpile JSX/TypeScript
      const transpiled = Babel.transform(wrappedSource, {
        presets: ['react', 'typescript'],
        filename: 'dynamic.tsx',
      });

      if (!transpiled.code) {
        return {Component: null, error: 'Transpilation failed'};
      }

      // Create component with injected APIs
      const createComponent = new Function('React', 'AbsoluteFill', 'useCurrentFrame', 'useVideoConfig', 'spring', 'interpolate', 'Sequence', `${transpiled.code}\nreturn DynamicComponent;`);

      const Component = createComponent(React, AbsoluteFill, useCurrentFrame, useVideoConfig, spring, interpolate, Sequence);

      return {Component, error: null};
    } catch (error) {
      return {
        Component: null,
        error: error instanceof Error ? error.message : 'Unknown compilation error',
      };
    }
  }, [code]);
}
</TabItem> <TabItem value="generated" label="Input code">
ts
const generatedCode = `import {useCurrentFrame, AbsoluteFill, spring, useVideoConfig} from 'remotion';

export const MyComposition = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  const scale = spring({
    frame,
    fps,
    config: {damping: 10, stiffness: 100},
  });

  return (
    <AbsoluteFill
      style={{
        backgroundColor: '#0f0f0f',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <div
        style={{
          fontSize: 120,
          color: 'white',
          transform: \`scale(\${scale})\`,
        }}
      >
        Hello World
      </div>
    </AbsoluteFill>
  );
};`;
</TabItem> </Tabs>

Security

Note that this code runs in the global scope of the browser.
It can access all global variables and functions.

For production applications, consider running the code in a sandboxed <iframe>, and setting a content security policy.
This is outside the scope of this guide.

See Also