mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-11 23:07:57 -05:00
feat: make scenes independent of names (#53)
This PR changes how scenes are modified/reloaded to not relay on their names anymore.
The name of the scene generator function no longer needs to match its file name
and can be even completely omitted:
```ts
export default makeKonvaScene(function* (view) {
// ...
});
```
This allows for bundling the animation for displaying on web.
Previously, minification would remove the name of the function breaking all time events.
BREAKING CHANGE: change the way scenes are imported
Scene files no longer need to follow the pattern: `[name].scene.tsx`.
When importing scenes in the project file, a dedicated `?scene` query param should be used:
```ts
import example from './scenes/example?scene';
export default new Project({
name: 'project',
scenes: [example],
});
```
Closes: #25
This commit is contained in:
38
package-lock.json
generated
38
package-lock.json
generated
@@ -21514,15 +21514,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.5.tgz",
|
||||
"integrity": "sha512-bRvrt9Tw8EGW4jj64aYFTnVg134E8hgDxyl/eEHnxiGqYk7/pTPss6CWlurqPOUzqvEoZkZ58Ws+Iu8MB87iMA==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.9.tgz",
|
||||
"integrity": "sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.47",
|
||||
"postcss": "^8.4.16",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.75.6"
|
||||
"rollup": ">=2.75.6 <2.77.0 || ~2.77.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -22419,7 +22419,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@motion-canvas/core",
|
||||
"version": "10.1.0",
|
||||
"version": "10.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
@@ -22448,7 +22448,7 @@
|
||||
},
|
||||
"packages/create": {
|
||||
"name": "@motion-canvas/create",
|
||||
"version": "10.1.0",
|
||||
"version": "10.2.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prompts": "^2.4.2"
|
||||
@@ -22457,8 +22457,8 @@
|
||||
"create-motion-canvas": "index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^10.1.0",
|
||||
"@motion-canvas/ui": "^10.1.0",
|
||||
"@motion-canvas/core": "^10.2.0",
|
||||
"@motion-canvas/ui": "^10.2.0",
|
||||
"@motion-canvas/vite-plugin": "^10.1.0"
|
||||
}
|
||||
},
|
||||
@@ -22497,15 +22497,15 @@
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*",
|
||||
"vite": "^3.0.5"
|
||||
"vite": "^3.0.9"
|
||||
}
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@motion-canvas/ui",
|
||||
"version": "10.1.0",
|
||||
"version": "10.2.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@motion-canvas/core": "^10.1.0",
|
||||
"@motion-canvas/core": "^10.2.0",
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"preact": "10.7.3",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -26291,8 +26291,8 @@
|
||||
"@motion-canvas/create": {
|
||||
"version": "file:packages/create",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^10.1.0",
|
||||
"@motion-canvas/ui": "^10.1.0",
|
||||
"@motion-canvas/core": "^10.2.0",
|
||||
"@motion-canvas/ui": "^10.2.0",
|
||||
"@motion-canvas/vite-plugin": "^10.1.0",
|
||||
"prompts": "^2.4.2"
|
||||
}
|
||||
@@ -26322,13 +26322,13 @@
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*",
|
||||
"vite": "^3.0.5"
|
||||
"vite": "^3.0.9"
|
||||
}
|
||||
},
|
||||
"@motion-canvas/ui": {
|
||||
"version": "file:packages/ui",
|
||||
"requires": {
|
||||
"@motion-canvas/core": "^10.1.0",
|
||||
"@motion-canvas/core": "^10.2.0",
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"preact": "10.7.3",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -38577,16 +38577,16 @@
|
||||
}
|
||||
},
|
||||
"vite": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.5.tgz",
|
||||
"integrity": "sha512-bRvrt9Tw8EGW4jj64aYFTnVg134E8hgDxyl/eEHnxiGqYk7/pTPss6CWlurqPOUzqvEoZkZ58Ws+Iu8MB87iMA==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.9.tgz",
|
||||
"integrity": "sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.14.47",
|
||||
"fsevents": "~2.3.2",
|
||||
"postcss": "^8.4.16",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.75.6"
|
||||
"rollup": ">=2.75.6 <2.77.0 || ~2.77.0"
|
||||
}
|
||||
},
|
||||
"vscode-oniguruma": {
|
||||
|
||||
@@ -5,7 +5,4 @@ module.exports = {
|
||||
testEnvironment: 'jsdom',
|
||||
setupFiles: ['jest-canvas-mock', './setup.test.ts'],
|
||||
testPathIgnorePatterns: ['setup.test.ts'],
|
||||
globals: {
|
||||
PROJECT_FILE_NAME: 'tests',
|
||||
},
|
||||
};
|
||||
|
||||
7
packages/core/project.d.ts
vendored
7
packages/core/project.d.ts
vendored
@@ -25,6 +25,11 @@ declare module '*.glsl' {
|
||||
export = value;
|
||||
}
|
||||
|
||||
declare module '*?scene' {
|
||||
const value: import('./lib/scenes/Scene').Scene;
|
||||
export = value;
|
||||
}
|
||||
|
||||
declare namespace JSX {
|
||||
type ElementClass = import('konva/lib/Node').Node;
|
||||
interface ElementChildrenAttribute {
|
||||
@@ -38,5 +43,3 @@ declare module 'colorjs.io' {
|
||||
}
|
||||
|
||||
declare type Callback = (...args: unknown[]) => void;
|
||||
|
||||
declare const PROJECT_FILE_NAME: string;
|
||||
|
||||
@@ -26,9 +26,10 @@ export class Meta<T extends Metadata = Metadata> {
|
||||
}
|
||||
private readonly data = new ValueDispatcher(<T>{version: META_VERSION});
|
||||
|
||||
private source: string | false;
|
||||
|
||||
private constructor(private readonly name: string) {}
|
||||
public constructor(
|
||||
private readonly name: string,
|
||||
private source: string | false = false,
|
||||
) {}
|
||||
|
||||
public getData() {
|
||||
return this.data.current;
|
||||
@@ -87,7 +88,16 @@ export class Meta<T extends Metadata = Metadata> {
|
||||
});
|
||||
}
|
||||
|
||||
private static metaLookup: Record<string, Meta> = {};
|
||||
/**
|
||||
* Load new metadata from a file.
|
||||
*
|
||||
* @param data - New metadata.
|
||||
*/
|
||||
public async loadData(data: T) {
|
||||
data.version ||= META_VERSION;
|
||||
this.data.current = data;
|
||||
}
|
||||
|
||||
private static sourceLookup: Record<string, Callback> = {};
|
||||
|
||||
static {
|
||||
@@ -97,53 +107,4 @@ export class Meta<T extends Metadata = Metadata> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Meta} object for the given entity.
|
||||
*
|
||||
* @param name - The name of the entity the metadata refers to.
|
||||
*
|
||||
* @typeParam T - The concrete type of the metadata. Depends on the entity.
|
||||
* See {@link SceneMetadata} and {@link ProjectMetadata} for
|
||||
* sample types.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static getMetaFor<T extends Metadata = Metadata>(
|
||||
name: string,
|
||||
): Meta<T> {
|
||||
this.metaLookup[name] ??= new Meta<T>(name);
|
||||
return <Meta<T>>this.metaLookup[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new version of metadata.
|
||||
*
|
||||
* @remarks
|
||||
* Called directly by meta files themselves.
|
||||
* Occurs during the initial load as well as during hot reloads.
|
||||
*
|
||||
* @param name - The Name of the entity this metadata refers to.
|
||||
* @param source - The absolute path to the source file.
|
||||
* @param rawData - New metadata as JSON.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static register(
|
||||
name: string,
|
||||
source: string | false,
|
||||
rawData: string,
|
||||
) {
|
||||
const meta = Meta.getMetaFor(name);
|
||||
meta.source = source;
|
||||
|
||||
try {
|
||||
const data: Metadata = JSON.parse(rawData);
|
||||
data.version ||= META_VERSION;
|
||||
meta.data.current = data;
|
||||
} catch (e) {
|
||||
console.error(`Error when parsing ${source}:`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Scene, SceneDescription} from './scenes';
|
||||
import {Scene} from './scenes';
|
||||
import {Meta, Metadata} from './Meta';
|
||||
import {EventDispatcher, ValueDispatcher} from './events';
|
||||
import {Size, CanvasColorSpace, CanvasOutputMimeType} from './types';
|
||||
@@ -11,7 +11,7 @@ export const ProjectSize = {
|
||||
|
||||
export interface ProjectConfig {
|
||||
name: string;
|
||||
scenes: SceneDescription[];
|
||||
scenes: Scene[];
|
||||
audio?: string;
|
||||
audioOffset?: number;
|
||||
canvas?: HTMLCanvasElement;
|
||||
@@ -131,7 +131,6 @@ export class Project {
|
||||
private _quality = 1;
|
||||
private _speed = 1;
|
||||
private framesPerSeconds = 30;
|
||||
private readonly sceneLookup: Record<string, Scene> = {};
|
||||
private previousScene: Scene = null;
|
||||
private background: string | false;
|
||||
private canvas: HTMLCanvasElement;
|
||||
@@ -153,7 +152,6 @@ export class Project {
|
||||
this.setSize(size);
|
||||
this.name = name;
|
||||
this.background = background;
|
||||
this.meta = Meta.getMetaFor(PROJECT_FILE_NAME);
|
||||
|
||||
if (audio) {
|
||||
this.audio.setSource(audio);
|
||||
@@ -163,15 +161,10 @@ export class Project {
|
||||
}
|
||||
|
||||
for (const scene of scenes) {
|
||||
if (this.sceneLookup[scene.name]) {
|
||||
console.error('Duplicated scene name: ', scene.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const instance = new scene.klass(this, scene.name, scene.config);
|
||||
instance.onReloaded.subscribe(() => this.reloaded.dispatch());
|
||||
this.sceneLookup[scene.name] = instance;
|
||||
scene.project = this;
|
||||
scene.onReloaded.subscribe(() => this.reloaded.dispatch());
|
||||
}
|
||||
this.scenes.current = [...scenes];
|
||||
|
||||
ifHot(hot => {
|
||||
hot.on('motion-canvas:export-ack', ({frame}) => {
|
||||
@@ -219,7 +212,7 @@ export class Project {
|
||||
}
|
||||
|
||||
private reloadAll() {
|
||||
for (const scene of Object.values(this.sceneLookup)) {
|
||||
for (const scene of this.scenes.current) {
|
||||
scene.reload();
|
||||
}
|
||||
}
|
||||
@@ -260,7 +253,7 @@ export class Project {
|
||||
const speed = this._speed;
|
||||
this._speed = 1;
|
||||
this.frame = 0;
|
||||
const scenes = Object.values(this.sceneLookup);
|
||||
const scenes = [...this.scenes.current];
|
||||
for (const scene of scenes) {
|
||||
await scene.recalculate();
|
||||
}
|
||||
@@ -336,13 +329,9 @@ export class Project {
|
||||
this.audio.setTime(this.framesToSeconds(this.frame + frameOffset));
|
||||
}
|
||||
|
||||
public updateScene(description: SceneDescription) {
|
||||
this.sceneLookup[description.name]?.reload(description.config);
|
||||
}
|
||||
|
||||
private findBestScene(frame: number): Scene {
|
||||
let lastScene = null;
|
||||
for (const scene of Object.values(this.sceneLookup)) {
|
||||
for (const scene of this.scenes.current) {
|
||||
if (!scene.isCached()) {
|
||||
console.warn(
|
||||
'Attempting to seek a project with an invalidated scene:',
|
||||
@@ -360,7 +349,7 @@ export class Project {
|
||||
}
|
||||
|
||||
private getNextScene(scene?: Scene): Scene {
|
||||
const scenes = Object.values(this.sceneLookup);
|
||||
const scenes = this.scenes.current;
|
||||
if (!scene) {
|
||||
return scenes[0];
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export abstract class GeneratorScene<T>
|
||||
implements Scene<ThreadGeneratorFactory<T>>, Threadable
|
||||
{
|
||||
public readonly timeEvents: TimeEvents;
|
||||
public readonly meta: Meta<SceneMetadata>;
|
||||
public project: Project;
|
||||
|
||||
public get firstFrame() {
|
||||
return this.cache.current.firstFrame;
|
||||
@@ -84,13 +84,11 @@ export abstract class GeneratorScene<T>
|
||||
private counters: Record<string, number> = {};
|
||||
|
||||
public constructor(
|
||||
public readonly project: Project,
|
||||
public readonly name: string,
|
||||
public readonly meta: Meta<SceneMetadata>,
|
||||
private runnerFactory: ThreadGeneratorFactory<T>,
|
||||
) {
|
||||
decorate(this.runnerFactory, threadable(name));
|
||||
|
||||
this.meta = Meta.getMetaFor(`${name}.scene`);
|
||||
this.timeEvents = new TimeEvents(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ export function makeKonvaScene(
|
||||
factory: ThreadGeneratorFactory<KonvaView>,
|
||||
): SceneDescription {
|
||||
return {
|
||||
name: factory.name,
|
||||
config: factory,
|
||||
klass: KonvaScene,
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface SceneMetadata extends Metadata {
|
||||
* {@link SceneDescription.config}.
|
||||
*/
|
||||
export interface SceneConstructor<T> {
|
||||
new (project: Project, name: string, config: T): Scene;
|
||||
new (name: string, meta: Meta, config: T): Scene;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,13 +34,6 @@ export interface SceneDescription<T = unknown> {
|
||||
* The class used to instantiate the scene.
|
||||
*/
|
||||
klass: SceneConstructor<T>;
|
||||
/**
|
||||
* The name of the scene.
|
||||
*
|
||||
* @remarks
|
||||
* Should match the first portion of the file name (`[name].scene.ts`).
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Configuration object.
|
||||
*/
|
||||
@@ -98,11 +91,8 @@ export interface Scene<T = unknown> {
|
||||
readonly name: string;
|
||||
/**
|
||||
* Reference to the project.
|
||||
*
|
||||
* @remarks
|
||||
* Will be passed as the first argument to the constructor.
|
||||
*/
|
||||
readonly project: Project;
|
||||
project: Project;
|
||||
readonly timeEvents: TimeEvents;
|
||||
readonly meta: Meta<SceneMetadata>;
|
||||
|
||||
|
||||
@@ -58,17 +58,8 @@ export class TimeEvents {
|
||||
private preserveTiming = true;
|
||||
|
||||
public constructor(private readonly scene: Scene) {
|
||||
const storageKey = `scene-${scene.project.name}-${scene.name}`;
|
||||
const storedEvents = localStorage.getItem(storageKey);
|
||||
if (storedEvents) {
|
||||
console.warn('Migrating localStorage to meta files');
|
||||
localStorage.setItem(`${storageKey}-backup`, storedEvents);
|
||||
localStorage.removeItem(storageKey);
|
||||
this.load(Object.values<TimeEvent>(JSON.parse(storedEvents)));
|
||||
} else {
|
||||
this.previousReference = scene.meta.getData().timeEvents ?? [];
|
||||
this.load(this.previousReference);
|
||||
}
|
||||
this.previousReference = scene.meta.getData().timeEvents ?? [];
|
||||
this.load(this.previousReference);
|
||||
|
||||
scene.onReloaded.subscribe(this.handleReload);
|
||||
scene.onRecalculated.subscribe(this.handleRecalculated);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Project} from '@motion-canvas/core/lib';
|
||||
|
||||
import example from './scenes/example.scene';
|
||||
import example from './scenes/example?scene';
|
||||
|
||||
export default new Project({
|
||||
name: 'project',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {makeKonvaScene} from '@motion-canvas/core/lib/scenes';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// Create your animations here
|
||||
|
||||
yield* waitFor(5);
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Project} from '@motion-canvas/core/lib';
|
||||
|
||||
import example from './scenes/example.scene';
|
||||
import example from './scenes/example?scene';
|
||||
|
||||
export default new Project({
|
||||
name: 'project',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {makeKonvaScene} from '@motion-canvas/core/lib/scenes';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// Create your animations here
|
||||
|
||||
yield* waitFor(5);
|
||||
@@ -51,16 +51,16 @@ must add an element to our scene.
|
||||
### Programming an animation
|
||||
|
||||
The scaffolding command will create several files for you, but for now we're
|
||||
going to focus on `src/scenes/example.scene.tsx`, which is where we can add our
|
||||
animations. Open `example.scene.tsx` in a text editor, and replace all code in
|
||||
going to focus on `src/scenes/example.tsx`, which is where we can add our
|
||||
animations. Open `example.tsx` in a text editor, and replace all code in
|
||||
the file with the following snippet.
|
||||
|
||||
```tsx
|
||||
```tsx title="src/scenes/example.tsx"
|
||||
import {makeKonvaScene} from '@motion-canvas/core/lib/scenes';
|
||||
import {Circle} from 'konva/lib/shapes/Circle';
|
||||
import {useRef} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
const myCircle = useRef();
|
||||
|
||||
view.add(
|
||||
@@ -99,7 +99,7 @@ function.
|
||||
import {makeKonvaScene} from '@motion-canvas/core/lib/scenes';
|
||||
|
||||
// highlight-next-line
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// animation code
|
||||
});
|
||||
```
|
||||
@@ -122,7 +122,7 @@ import {makeKonvaScene} from '@motion-canvas/core/lib/scenes';
|
||||
// highlight-next-line
|
||||
import {Circle} from 'konva/lib/shapes/Circle';
|
||||
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// highlight-start
|
||||
view.add(
|
||||
<Circle
|
||||
@@ -148,7 +148,7 @@ import {Circle} from 'konva/lib/shapes/Circle';
|
||||
// highlight-next-line
|
||||
import {useRef} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// highlight-next-line
|
||||
const myCircle = useRef();
|
||||
|
||||
@@ -214,7 +214,7 @@ import {Circle} from 'konva/lib/shapes/Circle';
|
||||
import {useRef} from '@motion-canvas/core/lib/utils';
|
||||
|
||||
// make a new konva scene and pass it the scene function
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// create a reference to store the circle
|
||||
const myCircle = useRef();
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ npm init @motion-canvas
|
||||
Upgrade the versions of all motion-canvas packages in your `package.json` file:
|
||||
|
||||
```diff
|
||||
- "@motion-canvas/core": "9.0.0",
|
||||
- "@motion-canvas/ui": "9.0.0",
|
||||
+ "@motion-canvas/core": "10.0.0",
|
||||
+ "@motion-canvas/ui": "10.0.0",
|
||||
- "@motion-canvas/core": "^9.0.0",
|
||||
- "@motion-canvas/ui": "^9.0.0",
|
||||
+ "@motion-canvas/core": "^10.0.0",
|
||||
+ "@motion-canvas/ui": "^10.0.0",
|
||||
```
|
||||
|
||||
To apply the changes, run:
|
||||
|
||||
71
packages/docs/docs/guides/migration/11.0.0.md
Normal file
71
packages/docs/docs/guides/migration/11.0.0.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: v11.0.0
|
||||
---
|
||||
|
||||
# Migrating to version 11.0.0
|
||||
|
||||
:::tip
|
||||
|
||||
If you're starting a new project, you can quickly scaffold it using:
|
||||
|
||||
```bash
|
||||
npm init @motion-canvas
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Install the new version
|
||||
|
||||
Upgrade the versions of all motion-canvas packages in your `package.json` file:
|
||||
|
||||
```diff
|
||||
- "@motion-canvas/core": "^10.0.0",
|
||||
- "@motion-canvas/ui": "^10.0.0",
|
||||
- "@motion-canvas/plugin-vite": "^10.0.0",
|
||||
+ "@motion-canvas/core": "^11.0.0",
|
||||
+ "@motion-canvas/ui": "^11.0.0",
|
||||
+ "@motion-canvas/plugin-vite": "^11.0.0",
|
||||
```
|
||||
|
||||
To apply the changes, run:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Update project file
|
||||
|
||||
Since version 11, scene file names no longer need to follow the pattern: `[name].scene.tsx`.
|
||||
Instead, a dedicated `?scene` query flag is used when importing a scene in the project file:
|
||||
|
||||
```diff title="src/project.tsx"
|
||||
- import example from './scenes/example.scene';
|
||||
+ import example from './scenes/example?scene';
|
||||
|
||||
export default new Project({
|
||||
name: 'project',
|
||||
scenes: [example],
|
||||
});
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
In the above example, we also changed the name of the scene file from `example.scene.tsx` to
|
||||
`example.tsx`. This way we avoid the redundant `scene` when importing it.
|
||||
|
||||
Of course, if you want, you can keep the old file name and import it as: `example.scene?scene`.
|
||||
|
||||
:::
|
||||
|
||||
This instructs our Vite plugin to turn the imported module into a scene.
|
||||
It will instantiate an adequate class, load its metadata, and set up hot module replacement.
|
||||
|
||||
Thanks to this change, the name of your scene generator function no longer
|
||||
needs to match its file name. In fact, it can be completely omitted:
|
||||
|
||||
```diff title="src/scenes/example.tsx"
|
||||
- export default makeKonvaScene(function* example(view) {
|
||||
+ export default makeKonvaScene(function* (view) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
@@ -16,6 +16,6 @@
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*",
|
||||
"vite": "^3.0.5"
|
||||
"vite": "^3.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Project} from '@motion-canvas/core/lib';
|
||||
|
||||
import example from './scenes/example.scene';
|
||||
import example from './scenes/example?scene';
|
||||
|
||||
export default new Project({
|
||||
name: 'project',
|
||||
|
||||
4
packages/template/src/scenes/example.meta
Normal file
4
packages/template/src/scenes/example.meta
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 1,
|
||||
"timeEvents": []
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import {makeKonvaScene} from '@motion-canvas/core/lib/scenes';
|
||||
import {waitFor} from '@motion-canvas/core/lib/flow';
|
||||
|
||||
export default makeKonvaScene(function* example(view) {
|
||||
export default makeKonvaScene(function* (view) {
|
||||
// Create your animations here
|
||||
|
||||
yield* waitFor(5);
|
||||
@@ -71,7 +71,6 @@ export default ({
|
||||
const editorId = 'virtual:editor';
|
||||
const resolvedEditorId = '\0' + editorId;
|
||||
const timeStamps: Record<string, number> = {};
|
||||
const projectName = path.parse(project).name;
|
||||
const outputPath = path.resolve(output);
|
||||
|
||||
let viteConfig: ResolvedConfig;
|
||||
@@ -100,15 +99,63 @@ export default ({
|
||||
return resolvedEditorId;
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
async load(id) {
|
||||
if (id === resolvedEditorId) {
|
||||
return source(
|
||||
`import '${styles}';`,
|
||||
`import editor from '${factory}';`,
|
||||
`import project from '${project}';`,
|
||||
`import project from '${project}?project';`,
|
||||
`editor(project);`,
|
||||
);
|
||||
}
|
||||
|
||||
const [base, query] = id.split('?');
|
||||
const {name, dir} = path.posix.parse(base);
|
||||
|
||||
if (query) {
|
||||
const params = new URLSearchParams(query);
|
||||
if (params.has('scene')) {
|
||||
const metaFile = `${name}.meta`;
|
||||
await createMeta(path.join(dir, metaFile));
|
||||
const sceneFile = `${name}`;
|
||||
|
||||
return source(
|
||||
`import meta from './${metaFile}';`,
|
||||
`import description from './${sceneFile}';`,
|
||||
`let scene;`,
|
||||
`if (import.meta.hot) {`,
|
||||
` scene = import.meta.hot.data.scene;`,
|
||||
`}`,
|
||||
`scene ??= new description.klass('${name}', meta, description.config);`,
|
||||
`if (import.meta.hot) {`,
|
||||
` import.meta.hot.accept();`,
|
||||
` if (import.meta.hot.data.scene) {`,
|
||||
` scene.reload(description.config);`,
|
||||
` } else {`,
|
||||
` import.meta.hot.data.scene = scene;`,
|
||||
` }`,
|
||||
`}`,
|
||||
`export default scene;`,
|
||||
);
|
||||
}
|
||||
|
||||
if (params.has('project')) {
|
||||
const metaFile = `${name}.meta`;
|
||||
await createMeta(path.join(dir, metaFile));
|
||||
const projectFile = `${name}`;
|
||||
|
||||
return source(
|
||||
`import '@motion-canvas/core/lib/patches/Factory';`,
|
||||
`import '@motion-canvas/core/lib/patches/Node';`,
|
||||
`import '@motion-canvas/core/lib/patches/Shape';`,
|
||||
`import '@motion-canvas/core/lib/patches/Container';`,
|
||||
`import meta from './${metaFile}';`,
|
||||
`import project from './${projectFile}';`,
|
||||
`project.meta = meta`,
|
||||
`export default project;`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
async transform(code, id) {
|
||||
const [base, query] = id.split('?');
|
||||
@@ -148,49 +195,22 @@ export default ({
|
||||
}
|
||||
|
||||
if (ext === '.meta') {
|
||||
const sourceFile = viteConfig.command === 'build' ? false : `'${id}'`;
|
||||
return source(
|
||||
`import {Meta} from '@motion-canvas/core/lib';`,
|
||||
`Meta.register(`,
|
||||
` '${name}',`,
|
||||
` ${viteConfig.command === 'build' ? false : `'${id}'`},`,
|
||||
` \`${code}\``,
|
||||
`);`,
|
||||
`let meta;`,
|
||||
`if (import.meta.hot) {`,
|
||||
` meta = import.meta.hot.data.meta;`,
|
||||
`}`,
|
||||
`meta ??= new Meta('${name}', ${sourceFile}, ${code});`,
|
||||
`if (import.meta.hot) {`,
|
||||
` import.meta.hot.accept();`,
|
||||
` import.meta.hot.data.meta = meta;`,
|
||||
`}`,
|
||||
`meta.loadData(${code});`,
|
||||
`export default meta;`,
|
||||
);
|
||||
}
|
||||
|
||||
if (name === projectName && (await this.resolve(project))?.id === id) {
|
||||
const metaFile = `${name}.meta`;
|
||||
await createMeta(path.join(dir, metaFile));
|
||||
|
||||
const imports =
|
||||
`import '@motion-canvas/core/lib/patches/Factory';` +
|
||||
`import '@motion-canvas/core/lib/patches/Node';` +
|
||||
`import '@motion-canvas/core/lib/patches/Shape';` +
|
||||
`import '@motion-canvas/core/lib/patches/Container';` +
|
||||
`import './${metaFile}';`;
|
||||
|
||||
return imports + code;
|
||||
}
|
||||
|
||||
if (name.endsWith('.scene')) {
|
||||
const metaFile = `${name}.meta`;
|
||||
await createMeta(path.join(dir, metaFile));
|
||||
|
||||
const imports =
|
||||
`import './${metaFile}';` +
|
||||
`import {useProject as __useProject} from '@motion-canvas/core/lib/utils';`;
|
||||
const hmr = source(
|
||||
`if (import.meta.hot) {`,
|
||||
` import.meta.hot.accept(module => {`,
|
||||
` __useProject()?.updateScene(module.default);`,
|
||||
` });`,
|
||||
`}`,
|
||||
);
|
||||
return imports + code + '\n' + hmr;
|
||||
}
|
||||
},
|
||||
handleHotUpdate(ctx) {
|
||||
const now = Date.now();
|
||||
@@ -272,12 +292,9 @@ export default ({
|
||||
jsx: 'automatic',
|
||||
jsxImportSource: '@motion-canvas/core/lib',
|
||||
},
|
||||
define: {
|
||||
PROJECT_FILE_NAME: `'${projectName}'`,
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: project,
|
||||
entry: `${project}?project`,
|
||||
formats: ['es'],
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user