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(renderer): renderer package #595

Closed
Closed
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
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
'examples',
'legacy',
'player',
'renderer',
'ui',
'vite-plugin',
],
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"examples:build": "npm run build -w packages/examples",
"player:serve": "npm run serve -w packages/player",
"player:build": "npm run build -w packages/player",
"renderer:build": "npm run build -w packages/renderer",
"renderer:watch": "npm run watch -w packages/renderer",
"renderer:render": "npm run render -w packages/renderer",
"docs:start": "npm run start -w packages/docs",
"docs:build": "npm run build -w packages/docs",
"docs:blog": "npm run blog -w packages/docs",
Expand Down
1 change: 1 addition & 0 deletions packages/renderer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
output
11 changes: 11 additions & 0 deletions packages/renderer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `renderer`

> TODO: description

## Usage

```
const renderer = require('renderer');

// TODO: DEMONSTRATE API
```
33 changes: 33 additions & 0 deletions packages/renderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@motion-canvas/renderer",
"version": "3.3.4",
"description": "A headless renderer for Motion Canvas",
"homepage": "https://motioncanvas.io/",
"license": "MIT",
"main": "dist/main.js",
"type": "module",
"directories": {
"dist": "dist",
"test": "__tests__"
},
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/motion-canvas/motion-canvas.git"
},
"scripts": {
"build": "ttsc",
"watch": "ttsc -w",
"render": "node ./dist/render.js"
},
"devDependencies": {
"@motion-canvas/core": "*",
"@motion-canvas/vite-plugin": "*",
"puppeteer": "^19.3.0"
},
"bugs": {
"url": "https://github.com/motion-canvas/motion-canvas/issues"
}
}
18 changes: 18 additions & 0 deletions packages/renderer/renderer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Motion Canvas Renderer</title>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAOwAAADsAEnxA+tAAABKklEQVQ4jcWTvUoDURCFv/y0A2tnZ95AW7FZwQdQSO9aRLAJbp0mPkGCTSCBmPQW8Q1SuKTU4AvkEeQO2K6MuSFmN+pCCg9cLhdmzpwzh1tK05RdUN6p+yeCftKK+kmrW4SguvGaNc3PGGRUVEE18x4Dl9dlheP76OZxMQUCIOzVa++/W2jENYaV7oqEWTMETMmhv7fi+w4i4IVhxaaeAq+9es0aL4CJqoaqOsmSrGNsxCZ16ideMeiMVPVrJyISqerC7IhIsN3CoGMeTfYceKARn6/sqOozcADkktmMcU3y5KeZrQQ4ARxwpqpts5O3kIEvGvnJ1vwB7PuquYgc5RVs4tZHeAe8WbOIlIA9r3IJU/DXcc61nXOpcy7I1hb9C5aOLTeHf/6NwCdua48fJxuYPgAAAABJRU5ErkJggg=="
/>
</head>
<body>
<h1>RENDERING!</h1>
<script type="module" src="{{source}}"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions packages/renderer/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {Project} from '@motion-canvas/core';
import {Renderer} from '@motion-canvas/core';

declare global {
interface Window {
onRenderComplete: () => void;
}
}

export const render = async (project: Project) => {
try {
const renderer = new Renderer(project);
await renderer.render({
...project.meta.getFullRenderingSettings(),
name: project.name,
});
} finally {
window.onRenderComplete();
}
};
35 changes: 35 additions & 0 deletions packages/renderer/src/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import fs from 'fs';
import * as path from 'path';
import puppeteer from 'puppeteer';
import {createServer} from 'vite';
import {fileURLToPath} from 'url';

const Root = fileURLToPath(new URL('.', import.meta.url));

(async () => {
console.log('Rendering...');

const project = process.argv[2];
const metaPath = `${Root}/../../template/src/${project}.meta`;
if (!fs.existsSync(metaPath))
return console.log(`Project couldn't be found.`);

const [browser, server] = await Promise.all([
puppeteer.launch({headless: true}),
createServer({
root: Root,
configFile: path.resolve(Root, '../vite.config.ts'),
server: {
port: 9000,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this port number be configurable?

Not exactly sure if this is related, but I noticed that running npm run serve offers the following convenience: will start on port 9000, and if it's not available, use the next available e.g. 9001.

Perhaps the port number here could follow a similar approach?

Copy link
Contributor

Choose a reason for hiding this comment

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

Choosing the next available port is a Vite functionality. It will do it automatically.
Tho it may be a good idea to actually check what port Vite picked using:

server.httpServer.address().port

and redirect Puppeteer there.

Right now we're using a hardcoded 9000 which will work most of the time but will fail if something is already running on 9000.

},
}).then(server => server.listen()),
]);

const page = await browser.newPage();
await page.goto(`http://localhost:9000/${project}/renderer`);

await page.exposeFunction('onRenderComplete', async () => {
await Promise.all([browser.close(), server.close()]);
console.log('Rendering complete.');
});
})();
19 changes: 19 additions & 0 deletions packages/renderer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist",
"strict": true,
"module": "esnext",
"esModuleInterop": true,
"target": "esnext",
"moduleResolution": "node",
"declaration": true,
"allowSyntheticDefaultImports": true,
"plugins": [
{
"transform": "@motion-canvas/internal/transformers/markdown-literals.js"
}
]
},
"include": ["src"]
}
12 changes: 12 additions & 0 deletions packages/renderer/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {defineConfig} from 'vite';
import motionCanvas from '@motion-canvas/vite-plugin';

export default defineConfig({
plugins: [
motionCanvas.default({
project: [`@motion-canvas/template/src/${process.argv[2]}.ts`],
editor: '@motion-canvas/renderer',
editorFileName: '../renderer.html',
}),
],
});
32 changes: 29 additions & 3 deletions packages/vite-plugin/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface MotionCanvasPluginConfig {
* @defaultValue '\@motion-canvas/ui'
*/
editor?: string;
editorFileName?: string;
/**
* Configuration of the Proxy used for remote sources
*
Expand All @@ -95,13 +96,15 @@ export default ({
output = './output',
bufferedAssets = /\.(wav|ogg)$/,
editor = '@motion-canvas/ui',
editorFileName = 'editor.html',
proxy,
}: MotionCanvasPluginConfig = {}): Plugin => {
const plugins: PluginOptions[] = [
{entryPoint: '@motion-canvas/core/lib/plugin/DefaultPlugin'},
];

const editorPath = path.dirname(require.resolve(editor));
const editorFile = fs.readFileSync(path.resolve(editorPath, 'editor.html'));
const editorFile = fs.readFileSync(path.resolve(editorPath, editorFileName));
const htmlParts = editorFile
.toString()
.replace('{{style}}', `/@fs/${path.resolve(editorPath, 'style.css')}`)
Expand All @@ -110,6 +113,7 @@ export default ({
const versions = JSON.stringify(getVersions());

const resolvedEditorId = '\0virtual:editor';
const resolvedRendererId = '\x00virtual:renderer';

const timeStamps: Record<string, number> = {};
const outputPath = path.resolve(output);
Expand Down Expand Up @@ -163,6 +167,18 @@ export default ({
const [base, query] = id.split('?');
const {name, dir} = path.posix.parse(base);

if (id.startsWith(resolvedRendererId)) {
const params = new URLSearchParams(query);
const name = params.get('project');
if (name && name in projectLookup) {
return source(
`import {render} from '${editor}';`,
`import project from '${projectLookup[name].url}?project';`,
`render(project);`,
);
}
}

if (id.startsWith(resolvedEditorId)) {
if (projects.length === 1) {
return source(
Expand Down Expand Up @@ -345,14 +361,24 @@ export default ({
const url = req.url
? new URL(req.url, `http://${req.headers.host}`)
: undefined;
if (url?.pathname === '/') {
const pathArray = url?.pathname.split('/');

if (url?.pathname === '/' || !pathArray) {
res.setHeader('Content-Type', 'text/html');
res.end(createHtml('/@id/__x00__virtual:editor'));
return;
}

const name = url?.pathname?.slice(1);
const name = pathArray[1];
const service = pathArray[2];

if (name && name in projectLookup) {
if (service === 'renderer') {
res.setHeader('Content-Type', 'text/html');
res.end(createHtml(`/@id/__x00__virtual:renderer?project=${name}`));
return;
}

res.setHeader('Content-Type', 'text/html');
res.end(createHtml(`/@id/__x00__virtual:editor?project=${name}`));
return;
Expand Down