Replace Vite with ESBuild for the playground (#213)
This commit is contained in:
parent
a6951effd8
commit
162174568b
7 changed files with 231 additions and 147 deletions
29
packages/playground/devEnv/Home.tsx
Normal file
29
packages/playground/devEnv/Home.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from 'react'
|
||||
|
||||
export const Home = ({groups}: {groups: {[groupName: string]: string[]}}) => (
|
||||
<ul>
|
||||
{Object.entries(groups).map(([groupName, modules]) => (
|
||||
<li key={`li-${groupName}`}>
|
||||
<span>{groupName}</span>
|
||||
<Group
|
||||
key={`group-${groupName}`}
|
||||
groupName={groupName}
|
||||
modules={modules}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
|
||||
const Group = (props: {groupName: string; modules: string[]}) => {
|
||||
const {groupName, modules} = props
|
||||
return (
|
||||
<ul>
|
||||
{modules.map((moduleName) => (
|
||||
<li key={`li-${moduleName}`}>
|
||||
<a href={`/${groupName}/${moduleName}`}>{moduleName}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
197
packages/playground/devEnv/build.tsx
Normal file
197
packages/playground/devEnv/build.tsx
Normal file
|
@ -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(
|
||||
<Home groups={mapValues(groups, (group) => 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(/<body>[\s\S]*<\/body>/, `<body>${homeHtml}</body>`),
|
||||
),
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
})
|
|
@ -24,6 +24,6 @@
|
|||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
<script src="%ENTRYPOINT%"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -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',
|
||||
},
|
||||
})
|
|
@ -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",
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* TODO explain this file
|
||||
* */
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
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 = () => (
|
||||
<ul>
|
||||
{Object.entries(groups).map(([groupName, modules]) => (
|
||||
<li key={`li-${groupName}`}>
|
||||
<span>{groupName}</span>
|
||||
<Group
|
||||
key={`group-${groupName}`}
|
||||
groupName={groupName}
|
||||
modules={modules}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
|
||||
const Group = (props: {groupName: string; modules: Record<string, $FixMe>}) => {
|
||||
const {groupName, modules} = props
|
||||
return (
|
||||
<ul>
|
||||
{Object.entries(modules).map(([moduleName, callback]) => (
|
||||
<li key={`li-${moduleName}`}>
|
||||
<a href={`/${groupName}/${moduleName}`}>{moduleName}</a>
|
||||
{/* <Group key={`group-${group}`} modules={modules} /> */}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
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'))
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"routes": [{"src": "/[^.]+", "dest": "/", "status": 200}]
|
||||
}
|
Loading…
Reference in a new issue