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

feat: plugin architecture #564

Merged
merged 1 commit into from Mar 20, 2023
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
5 changes: 5 additions & 0 deletions .eslintrc.json
Expand Up @@ -51,6 +51,11 @@
"modifiers": ["exported"],
"format": ["camelCase", "PascalCase"]
},
{
"selector": "variable",
"modifiers": ["exported", "global"],
"format": ["camelCase", "PascalCase", "UPPER_CASE"]
},
{
"selector": ["parameter", "variable"],
"modifiers": ["unused"],
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/app/Project.ts
@@ -1,10 +1,12 @@
import {FullSceneDescription} from '../scenes';
import {Logger} from './Logger';
import {ProjectMetadata} from './ProjectMetadata';
import type {ProjectMetadata} from './ProjectMetadata';
import type {Plugin} from '../plugin';

export interface ProjectSettings {
name?: string;
scenes: FullSceneDescription[];
plugins?: Plugin[];
logger?: Logger;
audio?: string;
audioOffset?: number;
Expand All @@ -14,6 +16,7 @@ export interface ProjectSettings {
export interface Project {
name: string;
scenes: FullSceneDescription[];
plugins: Plugin[];
logger: Logger;
meta: ProjectMetadata;
audio?: string;
Expand All @@ -30,7 +33,7 @@ export interface Project {
export function makeProject(settings: ProjectSettings) {
return {
logger: new Logger(),
meta: new ProjectMetadata(),
plugins: [],
...settings,
};
}
9 changes: 9 additions & 0 deletions packages/core/src/plugin/Plugin.ts
@@ -0,0 +1,9 @@
import type {Exporter, Project} from '../app';

/**
* Represents a runtime Motion Canvas plugin.
*/
export interface Plugin {
name: string;
exporters?(project: Project): Exporter[];
}
2 changes: 2 additions & 0 deletions packages/core/src/plugin/index.ts
@@ -0,0 +1,2 @@
export * from './Plugin';
export * from './makePlugin';
17 changes: 17 additions & 0 deletions packages/core/src/plugin/makePlugin.ts
@@ -0,0 +1,17 @@
import type {Plugin} from './Plugin';

/**
* A helper function for exporting Motion Canvas plugins.
*
* @param plugin - The plugin configuration.
*
* @example
* ```ts
* export default makePlugin({
* name: 'my-custom-plugin',
* });
* ```
*/
export function makePlugin(plugin: Plugin): Plugin {
return plugin;
}
28 changes: 26 additions & 2 deletions packages/vite-plugin/src/main.ts
Expand Up @@ -9,6 +9,7 @@ import {
setupEnvVarsForProxy,
} from './proxy-middleware';
import {getVersions} from './versions';
import {PluginOptions, isPlugin, PLUGIN_OPTIONS} from './plugins';

export interface MotionCanvasPluginConfig {
/**
Expand Down Expand Up @@ -96,6 +97,7 @@ export default ({
editor = '@motion-canvas/ui',
proxy,
}: MotionCanvasPluginConfig = {}): Plugin => {
const plugins: PluginOptions[] = [];
const editorPath = path.dirname(require.resolve(editor));
const editorFile = fs.readFileSync(path.resolve(editorPath, 'editor.html'));
const htmlParts = editorFile
Expand Down Expand Up @@ -140,6 +142,11 @@ export default ({
return {
name: 'motion-canvas',
async configResolved(resolvedConfig) {
plugins.push(
...resolvedConfig.plugins
.filter(isPlugin)
.map(plugin => plugin[PLUGIN_OPTIONS]),
);
viteConfig = resolvedConfig;
},
async load(id) {
Expand Down Expand Up @@ -206,15 +213,32 @@ export default ({
const metaFile = `${name}.meta`;
await createMeta(path.join(dir, metaFile));

const imports: string[] = [];
const pluginNames: string[] = ['...config.plugins'];
let index = 0;
for (const plugin of plugins) {
if (plugin.entryPoint) {
const pluginName = `plugin${index}`;
imports.push(`import ${pluginName} from '${plugin.entryPoint}'`);
pluginNames.push(pluginName);
index++;
}
}

return source(
...imports,
`import {ProjectMetadata} from '@motion-canvas/core/lib/app';`,
`import metaFile from './${metaFile}';`,
`import config from './${name}';`,
`metaFile.attach(config.meta)`,
`export default {`,
`const project = {`,
` name: '${name}',`,
` versions: ${versions},`,
` ...config,`,
` plugins: [${pluginNames.join(', ')}],`,
`};`,
`project.meta = new ProjectMetadata(project);`,
`metaFile.attach(project.meta)`,
`export default project;`,
);
}
}
Expand Down
38 changes: 38 additions & 0 deletions packages/vite-plugin/src/plugins.ts
@@ -0,0 +1,38 @@
import {Plugin as VitePlugin} from 'vite';

export const PLUGIN_OPTIONS = Symbol.for(
'@motion-canvas/vite-plugin/PLUGIN_OPTIONS',
);

export interface PluginOptions {
/**
* An entry point of the runtime plugin.
*
* @remarks
* While the Vite plugin can extend the backend functionality, this entry
* point lets you include custom runtime code that will be loaded by the
* browser.
*
* It should be a valid module specifier from which the plugin will be
* imported. The module should contain a default export of a runtime plugin.
*/
entryPoint: string;
}

/**
* Represents a Motion Canvas plugin.
*
* @remarks
* It's a normal Vite plugin that can provide additional configuration specific
* to Motion Canvas.
*/
export type Plugin = VitePlugin & {
/**
* The configuration specific to Motion Canvas.
*/
[PLUGIN_OPTIONS]: PluginOptions;
};

export function isPlugin(value: any): value is Plugin {
return value && typeof value === 'object' && PLUGIN_OPTIONS in value;
}