From dd0abaf87ce4783d87192c3de8b46d80f7c93efe Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:16:00 +0200 Subject: [PATCH 01/23] webcontainers to run generated app on docs --- docs/next.config.mjs | 16 +++ docs/package.json | 2 + docs/pages/toolpad/builder.tsx | 126 ++++++++++++++++++ docs/public/_headers | 5 + packages/create-toolpad-app/package.json | 6 + packages/create-toolpad-app/src/api.ts | 1 + .../create-toolpad-app/src/generateProject.ts | 4 +- packages/create-toolpad-app/tsup.config.ts | 4 +- pnpm-lock.yaml | 11 ++ 9 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 docs/pages/toolpad/builder.tsx create mode 100644 packages/create-toolpad-app/src/api.ts diff --git a/docs/next.config.mjs b/docs/next.config.mjs index 278d3f70fe7..04fda67e7fb 100644 --- a/docs/next.config.mjs +++ b/docs/next.config.mjs @@ -2,6 +2,7 @@ import * as path from 'path'; import * as url from 'url'; import { createRequire } from 'module'; +import { headers } from 'next/headers.js'; import { LANGUAGES, LANGUAGES_IGNORE_PAGES, LANGUAGES_IN_PROGRESS } from './config.js'; const currentDirectory = url.fileURLToPath(new URL('.', import.meta.url)); @@ -164,5 +165,20 @@ export default withDocsInfra({ permanent: false, }, ], + headers: async () => [ + { + source: '/toolpad/builder', + headers: [ + { + key: 'Cross-Origin-Embedder-Policy', + value: 'require-corp', + }, + { + key: 'Cross-Origin-Opener-Policy', + value: 'same-origin', + }, + ], + }, + ], }), }); diff --git a/docs/package.json b/docs/package.json index 612af60517b..c92f337db31 100644 --- a/docs/package.json +++ b/docs/package.json @@ -42,6 +42,7 @@ "@trendmicro/react-interpolate": "0.5.5", "@types/lodash": "4.17.6", "@types/react-router-dom": "5.3.3", + "@webcontainer/api": "1.3.0-internal.1", "ast-types": "0.14.2", "autoprefixer": "10.4.19", "babel-plugin-module-resolver": "5.0.2", @@ -53,6 +54,7 @@ "clipboard-copy": "4.0.1", "clsx": "2.1.1", "core-js": "2.6.12", + "create-toolpad-app": "workspace:*", "cross-env": "7.0.3", "date-fns-jalali": "2.29.3-0", "dayjs": "1.11.11", diff --git a/docs/pages/toolpad/builder.tsx b/docs/pages/toolpad/builder.tsx new file mode 100644 index 00000000000..3ca5e9aa2f5 --- /dev/null +++ b/docs/pages/toolpad/builder.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import { WebContainer } from '@webcontainer/api'; +import { generateProject } from 'create-toolpad-app'; +import { Box, CircularProgress, CssBaseline, GlobalStyles } from '@mui/material'; + +const files = {}; + +function ensureFolder(folders: string[], containingFolder: Record) { + if (folders.length <= 0) { + return containingFolder; + } + const [first, ...rest] = folders; + let folder = containingFolder[first]; + if (!folder) { + folder = { directory: {} }; + containingFolder[first] = folder; + } + return ensureFolder(rest, folder.directory); +} + +for (const [name, { content }] of generateProject({ name: 'demo' })) { + const segments = name.split('/'); + const folders = segments.slice(0, segments.length - 1); + const folder = ensureFolder(folders, files); + const file = segments[segments.length - 1]; + folder[file] = { file: { contents: content } }; +} + +async function installDependencies(instance: WebContainer) { + // Install dependencies + const installProcess = await instance.spawn('npm', ['install']); + installProcess.output.pipeTo( + new WritableStream({ + write(data) { + // eslint-disable-next-line no-console + console.log(data); + }, + }), + ); + // Wait for install command to exit + return installProcess.exit; +} + +export default function Builder() { + const frameRef = React.useRef(null); + const webcontainerPromiseRef = React.useRef | null>(null); + + React.useEffect(() => { + if (!frameRef.current) { + throw new Error('Frame not found'); + } + + if (webcontainerPromiseRef.current) { + return; + } + + const frame = frameRef.current; + + const webcontainerPromise = webcontainerPromiseRef.current ?? WebContainer.boot(); + webcontainerPromiseRef.current = webcontainerPromise; + + webcontainerPromise.then(async (instance) => { + if (webcontainerPromiseRef.current !== webcontainerPromise) { + return; + } + + await instance.mount(files); + + const exitCode = await installDependencies(instance); + if (exitCode !== 0) { + throw new Error('Installation failed'); + } + + // Run `npm run dev` to start the next.js dev server + const devProcess = await instance.spawn('npm', ['run', 'dev']); + devProcess.output.pipeTo( + new WritableStream({ + write(data) { + // eslint-disable-next-line no-console + console.log(data); + }, + }), + ); + + // Wait for `server-ready` event + instance.on('server-ready', (port, url) => { + frame.src = url; + }); + }); + + return () => { + // webcontainerPromise.then((instance) => instance.teardown()); + }; + }, []); + + const [loading, setLoading] = React.useState(true); + + return ( + + + + {loading ? ( + + + + ) : null} + +