Complete production-ready guide for Motion Canvas with ESM/CommonJS workarounds, full setup templates, and troubleshooting for programmatic video creation using TypeScript
Complete production-ready skill for creating programmatic videos using Motion Canvas, including critical ESM/CommonJS workarounds, full configuration templates, and comprehensive troubleshooting.
IMPORTANT: The @motion-canvas/vite-plugin package is distributed as CommonJS, which causes import errors in modern ESM projects. The standard import motionCanvas from '@motion-canvas/vite-plugin' WILL NOT WORK.
You MUST use the createRequire workaround documented in the Setup section below.
Use this skill whenever you are dealing with Motion Canvas code to obtain domain-specific knowledge about:
Motion Canvas allows you to create videos using:
yield* syntax# Create project directory
mkdir my-motion-canvas-project
cd my-motion-canvas-project
# Initialize package.json
npm init -y
CRITICAL: Add "type": "module" to enable ESM imports.
{
"name": "my-motion-canvas-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
CRITICAL: Must include @motion-canvas/ui - the plugin will fail without it.
npm install --save-dev @motion-canvas/core @motion-canvas/2d @motion-canvas/vite-plugin @motion-canvas/ui vite typescript
my-motion-canvas-project/
├── package.json # "type": "module" required
├── vite.config.js # Use .js NOT .ts (see Step 5)
├── tsconfig.json # TypeScript configuration
├── index.html # HTML entry point
└── src/
├── project.ts # Project configuration with scenes
└── scenes/
└── example.tsx # Animation scene
CRITICAL: Use vite.config.js (NOT .ts) with the createRequire workaround.
File: vite.config.js
import {defineConfig} from 'vite';
import {createRequire} from 'module';
// WORKAROUND: @motion-canvas/vite-plugin is CommonJS, must use require
const require = createRequire(import.meta.url);
const motionCanvasModule = require('@motion-canvas/vite-plugin');
const motionCanvas = motionCanvasModule.default || motionCanvasModule;
export default defineConfig({
plugins: [
motionCanvas({
project: './src/project.ts',
}),
],
});
Why .js instead of .ts?
createRequire workaround works reliably in plain JavaScriptCRITICAL: Include esModuleInterop and allowSyntheticDefaultImports.
File: tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020", "DOM"],
"jsx": "react-jsx",
"jsxImportSource": "@motion-canvas/2d/lib",
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
File: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Motion Canvas Project</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/project.ts"></script>
</body>
</html>
File: src/project.ts
import {makeProject} from '@motion-canvas/core';
import example from './scenes/example?scene';
export default makeProject({
scenes: [example],
});
File: src/scenes/example.tsx
import {makeScene2D} from '@motion-canvas/2d/lib/scenes';
import {Circle} from '@motion-canvas/2d/lib/components';
import {createRef} from '@motion-canvas/core/lib/utils';
import {all} from '@motion-canvas/core/lib/flow';
export default makeScene2D(function* (view) {
const circleRef = createRef<Circle>();
view.add(
<Circle
ref={circleRef}
size={70}
fill="#e13238"
/>,
);
// Animate circle size and position
yield* circleRef().size(140, 1);
yield* circleRef().position.x(300, 1);
yield* circleRef().fill('#e6a700', 1);
// Parallel animations
yield* all(
circleRef().scale(1.5, 0.5),
circleRef().rotation(360, 1)
);
});
npm run dev
Open browser at http://localhost:5173 to see the Motion Canvas editor.
TypeError: motionCanvas is not a functionCause: ESM/CommonJS interoperability issue with @motion-canvas/vite-plugin
Solution: Use the createRequire workaround in vite.config.js (see Step 5)
// ❌ WRONG - Will not work
import motionCanvas from '@motion-canvas/vite-plugin';
// ✅ CORRECT - Use createRequire
import {createRequire} from 'module';
const require = createRequire(import.meta.url);
const motionCanvasModule = require('@motion-canvas/vite-plugin');
const motionCanvas = motionCanvasModule.default || motionCanvasModule;
Cannot find module '@motion-canvas/ui'Cause: Missing required dependency
Solution: Install the UI package:
npm install --save-dev @motion-canvas/ui
Property 'default' does not exist on type ...Cause: TypeScript configuration missing ESM interop settings
Solution: Add to tsconfig.json:
{
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
The CJS build of Vite's Node API is deprecatedStatus: This is a known warning and can be safely ignored. It appears because @motion-canvas/vite-plugin is CommonJS. The workaround ensures functionality despite the warning.
Failed to resolve import "*.tsx?scene"Cause: Vite plugin not properly loaded or configured
Solution:
vite.config.js has the correct workaroundproject path points to correct file: './src/project.ts'?scene suffix: import example from './scenes/example?scene';Solution:
tsconfig.json includes all required options (see Step 6)jsxImportSource is set to @motion-canvas/2d/libRead individual rule files for detailed explanations and code examples:
For additional topics like scenes, shapes, text rendering, audio synchronization, and advanced features, refer to the comprehensive Motion Canvas official documentation.
This is a complete, tested project structure that works out of the box:
my-motion-canvas-project/
├── package.json
│ {
│ "name": "my-motion-canvas-project",
│ "type": "module",
│ "scripts": {
│ "dev": "vite",
│ "build": "vite build"
│ },
│ "devDependencies": {
│ "@motion-canvas/core": "^3.0.0",
│ "@motion-canvas/2d": "^3.0.0",
│ "@motion-canvas/vite-plugin": "^3.0.0",
│ "@motion-canvas/ui": "^3.0.0",
│ "vite": "^5.0.0",
│ "typescript": "^5.0.0"
│ }
│ }
│
├── vite.config.js (with createRequire workaround)
├── tsconfig.json (with esModuleInterop)
├── index.html
└── src/
├── project.ts (makeProject with scenes array)
└── scenes/
└── example.tsx (makeScene2D with animations)
@motion-canvas/uifunction* and yield* syntax"type": "module" in package.json@motion-canvas/vite-plugin@motion-canvas/uiesModuleInterop in tsconfig.jsonvite.config.ts instead of vite.config.js?scene suffix in scene imports