diff --git a/packages/playground/devEnv/Home.tsx b/packages/playground/devEnv/Home.tsx
new file mode 100644
index 0000000..e7b7c49
--- /dev/null
+++ b/packages/playground/devEnv/Home.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+
+export const Home = ({groups}: {groups: {[groupName: string]: string[]}}) => (
+
+ {Object.entries(groups).map(([groupName, modules]) => (
+ -
+ {groupName}
+
+
+ ))}
+
+)
+
+const Group = (props: {groupName: string; modules: string[]}) => {
+ const {groupName, modules} = props
+ return (
+
+ )
+}
diff --git a/packages/playground/devEnv/build.tsx b/packages/playground/devEnv/build.tsx
new file mode 100644
index 0000000..c88bcbc
--- /dev/null
+++ b/packages/playground/devEnv/build.tsx
@@ -0,0 +1,197 @@
+import {readdirSync} from 'fs'
+import {writeFile, readFile} from 'fs/promises'
+import path from 'path'
+import type {BuildOptions} from 'esbuild'
+import esbuild from 'esbuild'
+import type {ServerResponse} from 'http'
+import {definedGlobals} from '../../../theatre/devEnv/buildUtils'
+import {mapValues} from 'lodash-es'
+import {createServer, request} from 'http'
+import {spawn} from 'child_process'
+import React from 'react'
+import {renderToStaticMarkup} from 'react-dom/server'
+import {Home} from './Home'
+
+const playgroundDir = path.join(__dirname, '..')
+const buildDir = path.join(playgroundDir, 'build')
+const sharedDir = path.join(playgroundDir, 'src/shared')
+const personalDir = path.join(playgroundDir, 'src/personal')
+const testDir = path.join(playgroundDir, 'src/tests')
+
+const dev = /^--dev|-d$/.test(process.argv[process.argv.length - 1])
+const port = 8080
+
+const clients: ServerResponse[] = []
+
+type Groups = {
+ [group: string]: {
+ [module: string]: {
+ entryDir: string
+ outDir: string
+ }
+ }
+}
+
+// Collect all entry directories per module per group
+const groups: Groups = Object.fromEntries(
+ [sharedDir, personalDir, testDir]
+ .map((groupDir) => {
+ try {
+ return [
+ path.basename(groupDir),
+ Object.fromEntries(
+ readdirSync(groupDir).map((moduleDir) => [
+ path.basename(moduleDir),
+ {
+ entryDir: path.join(groupDir, moduleDir),
+ outDir: path.join(buildDir, path.basename(groupDir), moduleDir),
+ },
+ ]),
+ ),
+ ]
+ } catch (e) {
+ // If the group dir doesn't exist, we just set its entry to undefined
+ return [path.basename(groupDir), undefined]
+ }
+ })
+ // and then filter it out.
+ .filter((entry) => entry[1] !== undefined),
+)
+
+// Collect all entry files
+const entryPoints = Object.values(groups)
+ .flatMap((group) => Object.values(group))
+ .map((module) => path.join(module.entryDir, 'index.tsx'))
+
+// Collect all output directories
+const outDirs = Object.values(groups).flatMap((group) =>
+ Object.values(group).map((module) => module.outDir),
+)
+
+// Render home page contents
+const homeHtml = renderToStaticMarkup(
+ Object.keys(group))} />,
+)
+
+const config: BuildOptions = {
+ entryPoints,
+ bundle: true,
+ sourcemap: true,
+ outdir: path.join(playgroundDir, 'build'),
+ target: ['firefox88'],
+ loader: {
+ '.png': 'file',
+ '.glb': 'file',
+ '.gltf': 'file',
+ '.svg': 'dataurl',
+ },
+ define: {
+ ...definedGlobals,
+ 'window.__IS_VISUAL_REGRESSION_TESTING': 'true',
+ },
+ banner: dev
+ ? {
+ js: ' (() => new EventSource("/esbuild").onmessage = () => location.reload())();',
+ }
+ : undefined,
+ watch: dev && {
+ onRebuild(error) {
+ // Notify clients on rebuild
+ clients.forEach((res) => res.write('data: update\n\n'))
+ clients.length = 0
+ console.log(error ? error : 'Reloading...')
+ },
+ },
+}
+
+esbuild
+ .build(config)
+ .then(async () => {
+ // Read index.html template
+ const index = await readFile(path.join(__dirname, 'index.html'), 'utf8')
+ await Promise.all([
+ // Write home page
+ writeFile(
+ path.join(buildDir, 'index.html'),
+ index.replace(/[\s\S]*<\/body>/, `${homeHtml}`),
+ ),
+ // Write module pages
+ ...outDirs.map((outDir) =>
+ writeFile(
+ path.join(outDir, 'index.html'),
+ // Substitute %ENTRYPOINT% placeholder with the output file path
+ index.replace(
+ '%ENTRYPOINT%',
+ path.join('/', path.relative(buildDir, outDir), 'index.js'),
+ ),
+ ),
+ ),
+ ])
+ })
+ .catch((err) => {
+ console.log(err)
+ return process.exit(1)
+ })
+ .then(() => {
+ // Only start dev server in dev, otherwise just run build and that's it
+ if (!dev) {
+ return
+ }
+
+ // We start ESBuild serve with no build config because it doesn't need to build
+ // anything, we are already using ESBuild watch.
+ esbuild
+ .serve({servedir: path.join(playgroundDir, 'build')}, {})
+ .then(({port: esbuildPort}) => {
+ // Create proxy
+ createServer((req, res) => {
+ const {url, method, headers} = req
+ // If special /esbuild url requested, subscribe clients to changes
+ if (req.url === '/esbuild') {
+ return clients.push(
+ res.writeHead(200, {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ Connection: 'keep-alive',
+ }),
+ )
+ }
+ // Otherwise forward requests to ESBuild server
+ req.pipe(
+ request(
+ {
+ hostname: '0.0.0.0',
+ port: esbuildPort,
+ path: url,
+ method,
+ headers,
+ },
+ (prxRes) => {
+ res.writeHead(prxRes.statusCode!, prxRes.headers)
+ prxRes.pipe(res, {end: true})
+ },
+ ),
+ {end: true},
+ )
+ }).listen(port, () => {
+ console.log('Playground running at', 'http://localhost:' + port)
+ })
+
+ // If not in CI, try to spawn a browser
+ if (!process.env.CI) {
+ setTimeout(() => {
+ const open = {
+ darwin: ['open'],
+ linux: ['xdg-open'],
+ win32: ['cmd', '/c', 'start'],
+ }
+ const platform = process.platform as keyof typeof open
+ if (clients.length === 0)
+ spawn(open[platform][0], [
+ ...open[platform].slice(1),
+ `http://localhost:${port}`,
+ ])
+ }, 1000)
+ }
+ })
+ })
diff --git a/packages/playground/src/index.html b/packages/playground/devEnv/index.html
similarity index 90%
rename from packages/playground/src/index.html
rename to packages/playground/devEnv/index.html
index aa0685a..26c1d45 100644
--- a/packages/playground/src/index.html
+++ b/packages/playground/devEnv/index.html
@@ -24,6 +24,6 @@
-
+
diff --git a/packages/playground/devEnv/vite.config.ts b/packages/playground/devEnv/vite.config.ts
deleted file mode 100644
index cc51854..0000000
--- a/packages/playground/devEnv/vite.config.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import {defineConfig} from 'vite'
-import react from '@vitejs/plugin-react'
-import path from 'path'
-import {getAliasesFromTsConfigForRollup} from '../../../devEnv/getAliasesFromTsConfig'
-import {definedGlobals} from '../../../theatre/devEnv/buildUtils'
-
-/*
-We're using vite instead of the older pure-esbuild setup. The tradeoff is
-that page reloads are much slower (>1s diff), while hot reload of react components
-are instantaneous and of course, they preserve state.
-
-@todo Author feels that the slow reloads are quite annoying and disruptive to flow,
-so if you find a way to make them faster, please do.
-*/
-
-const playgroundDir = path.join(__dirname, '..')
-
-const port = 8080
-
-// https://vitejs.dev/config/
-export default defineConfig({
- root: path.join(playgroundDir, './src'),
- build: {
- outDir: '../build',
- minify: false,
- emptyOutDir: true,
- },
-
- assetsInclude: ['**/*.gltf', '**/*.glb'],
- server: {
- port,
- },
-
- plugins: [react()],
- resolve: {
- /*
- This will alias paths like `@theatre/core` to `path/to/theatre/core/src/index.ts` and so on,
- so vite won't treat the monorepo's packages as externals and won't pre-bundle them.
- */
- alias: [...getAliasesFromTsConfigForRollup()],
- },
- define: {
- ...definedGlobals,
- 'window.__IS_VISUAL_REGRESSION_TESTING': 'true',
- },
-})
diff --git a/packages/playground/package.json b/packages/playground/package.json
index 20fb2c4..80dcc67 100644
--- a/packages/playground/package.json
+++ b/packages/playground/package.json
@@ -8,13 +8,12 @@
"dist/**/*"
],
"scripts": {
- "serve": "vite --config ./devEnv/vite.config.ts",
- "build:static": "vite --config ./devEnv/vite.config.ts build",
- "build:preview": "vite --config ./devEnv/vite.config.ts preview",
+ "serve": "node -r esbuild-register devEnv/build.tsx -d",
+ "build": "node -r esbuild-register devEnv/build.tsx",
+ "build:static": "yarn build",
"typecheck": "yarn run build",
"test": "playwright test --config=devEnv/playwright.config.ts",
- "test:ci": "percy exec -- playwright test --reporter=dot --config=devEnv/playwright.config.ts --project=chromium",
- "build": "tsc --build ./tsconfig.json"
+ "test:ci": "percy exec -- playwright test --reporter=dot --config=devEnv/playwright.config.ts --project=chromium"
},
"devDependencies": {
"@percy/cli": "^1.3.0",
diff --git a/packages/playground/src/index.tsx b/packages/playground/src/index.tsx
deleted file mode 100644
index 4655163..0000000
--- a/packages/playground/src/index.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * TODO explain this file
- * */
-///
-
-import type {$FixMe} from '@theatre/shared/utils/types'
-import {mapKeys} from 'lodash-es'
-import React from 'react'
-import ReactDOM from 'react-dom'
-
-const groups = {
- shared: mapKeys(import.meta.glob('./shared/*/index.tsx'), (_, path) =>
- pathToModuleName(path),
- ),
- personal: mapKeys(import.meta.glob('./personal/*/index.tsx'), (_, path) =>
- pathToModuleName(path),
- ),
- tests: mapKeys(import.meta.glob('./tests/*/index.tsx'), (_, path) =>
- pathToModuleName(path),
- ),
-}
-
-function pathToModuleName(path: string): string {
- const matches = path.match(
- /^\.\/(shared|personal|tests)\/([a-zA-Z0-9\-\s]+)\/index\.tsx$/,
- )
-
- if (!matches) {
- throw new Error(
- `module ${path} has invalid characters in its path. Valid names should match the regexp above this line.`,
- )
- }
-
- return matches[2]
-}
-
-const Home = () => (
-
- {Object.entries(groups).map(([groupName, modules]) => (
- -
- {groupName}
-
-
- ))}
-
-)
-
-const Group = (props: {groupName: string; modules: Record}) => {
- const {groupName, modules} = props
- return (
-
- {Object.entries(modules).map(([moduleName, callback]) => (
- -
- {moduleName}
- {/* */}
-
- ))}
-
- )
-}
-
-const currentPathname = document.location.pathname
-
-if (currentPathname === '/') {
- renderHome()
-} else {
- const parts = currentPathname.match(
- /^\/(shared|personal|tests)\/([a-zA-Z0-9\-]+)$/,
- )
- if (parts) {
- const [, groupName, moduleName] = parts
- const group = groups[groupName as 'shared' | 'personal']
- if (!group) {
- throw new Error(`Unknown group ${groupName}`)
- }
- const module = group[moduleName]
- if (!module) {
- throw new Error(`Unknown module ${moduleName}`)
- }
- module()
- } else {
- throw new Error(`Unknown path ${currentPathname}`)
- }
-}
-
-function renderHome() {
- ReactDOM.render(React.createElement(Home), document.getElementById('root'))
-}
diff --git a/packages/playground/vercel.json b/packages/playground/vercel.json
deleted file mode 100644
index 3955a13..0000000
--- a/packages/playground/vercel.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "routes": [{"src": "/[^.]+", "dest": "/", "status": 200}]
-}