Switch to Vite (#434)

This commit is contained in:
Aria 2023-07-16 22:19:21 +02:00 committed by GitHub
parent 2052824aca
commit b83164f26f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 3326 additions and 787 deletions

View file

@ -52,3 +52,7 @@ runs:
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
shell: bash shell: bash
run: yarn workspace playground run playwright install --with-deps run: yarn workspace playground run playwright install --with-deps
- name: Update browserlist
shell: bash
run: npx browserslist@latest --update-db

View file

@ -12,7 +12,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x] node-version: [18.x]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -28,7 +28,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x] node-version: [18.x]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -45,7 +45,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x] node-version: [18.x]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -61,7 +61,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x] node-version: [18.x]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -78,7 +78,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x] node-version: [18.x]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -100,7 +100,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [16.x] node-version: [18.x]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View file

@ -102,7 +102,7 @@ jobs:
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16.x node-version: 18.x
- uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/yarn-nm-install
- name: Build the Theatre.js packages - name: Build the Theatre.js packages

View file

@ -9,7 +9,7 @@
"dependencies": { "dependencies": {
"@cspotcode/zx": "^6.1.2", "@cspotcode/zx": "^6.1.2",
"node-cleanup": "^2.1.2", "node-cleanup": "^2.1.2",
"playwright": "^1.28.1", "playwright": "^1.29.1",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"verdaccio": "^5.10.2", "verdaccio": "^5.10.2",
"verdaccio-auth-memory": "^10.2.0", "verdaccio-auth-memory": "^10.2.0",

View file

@ -20,15 +20,21 @@ module.exports = {
automock: false, automock: false,
transform: { transform: {
'^.+\\.tsx?$': [ '^.+\\.tsx?$': [
'esbuild-jest', 'jest-esbuild',
{ {
sourcemap: true, sourcemap: true,
supported: {
'dynamic-import': false,
},
}, },
], ],
'^.+\\.js$': [ '^.+\\.js$': [
'esbuild-jest', 'jest-esbuild',
{ {
sourcemap: true, sourcemap: true,
supported: {
'dynamic-import': false,
},
}, },
], ],
}, },

View file

@ -44,16 +44,17 @@
"@microsoft/api-extractor": "^7.28.6", "@microsoft/api-extractor": "^7.28.6",
"@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7", "@typescript-eslint/parser": "^5.30.7",
"esbuild": "^0.16.7", "esbuild": "^0.18.13",
"esbuild-jest": "^0.5.0",
"eslint": "^8.20.0", "eslint": "^8.20.0",
"eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.30.1", "eslint-plugin-react": "^7.30.1",
"eslint-plugin-tsdoc": "^0.2.16", "eslint-plugin-tsdoc": "^0.2.16",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^2.0.0",
"fast-glob": "^3.3.0",
"husky": "^6.0.0", "husky": "^6.0.0",
"jest": "^29.3.1", "jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.3.1",
"jest-esbuild": "^0.3.0",
"jsonc-parser": "^3.1.0", "jsonc-parser": "^3.1.0",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"node-gyp": "^9.1.0", "node-gyp": "^9.1.0",

View file

@ -2,3 +2,4 @@
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/build /build
/dist

View file

@ -1,339 +0,0 @@
import type {BuildOptions} from 'esbuild'
import esbuild from 'esbuild'
import {readdir, readFile, stat, writeFile} from 'fs/promises'
import {mapValues} from 'lodash-es'
import path from 'path'
import React from 'react'
import {renderToStaticMarkup} from 'react-dom/server'
import {ServerStyleSheet} from 'styled-components'
import {definedGlobals} from '../../../theatre/devEnv/definedGlobals'
import {createEsbuildLiveReloadTools} from './createEsbuildLiveReloadTools'
import {createProxyServer} from './createProxyServer'
import {PlaygroundPage} from './home/PlaygroundPage'
import {openForOS} from './openForOS'
import {tryMultiplePorts} from './tryMultiplePorts'
const playgroundDir = (folder: string) => path.join(__dirname, '..', folder)
const buildDir = playgroundDir('build')
const srcDir = playgroundDir('src')
const sharedDir = playgroundDir('src/shared')
const personalDir = playgroundDir('src/personal')
const testDir = playgroundDir('src/tests')
async function start(options: {
/** enable live reload and watching stuff */
dev: boolean
/** make some UI elements predictable by setting the __IS_VISUAL_REGRESSION_TESTING value on window */
isVisualRegressionTesting: boolean
serve?: {
findAvailablePort: boolean
openBrowser: boolean
/** defaults to 8080 */
defaultPort?: number
}
}): Promise<void> {
const defaultPort = options.serve?.defaultPort ?? 8080
const liveReload =
options.serve && options.dev ? createEsbuildLiveReloadTools() : undefined
type PlaygroundExample = {
useHtml?: string
entryFilePath: string
outDir: string
}
type Groups = {
[group: string]: {
[module: string]: PlaygroundExample
}
}
// Collect all entry directories per module per group
const groups: Groups = await Promise.all(
[sharedDir, personalDir, testDir].map(async (groupDir) => {
let groupDirItems: string[]
try {
groupDirItems = await readdir(groupDir)
} catch (error) {
// If the group dir doesn't exist, we just set its entry to undefined
return [path.basename(groupDir), undefined]
}
const allEntries = await Promise.all(
groupDirItems.map(
async (
moduleDirName,
): Promise<[string, PlaygroundExample | undefined]> => {
const playgroundKey = path.basename(moduleDirName)
const entryFilePath = path.join(
groupDir,
moduleDirName,
'index.tsx',
)
if (
!(await stat(entryFilePath)
.then((s) => s.isFile())
.catch(() => false))
)
return [playgroundKey, undefined]
const playgroundExample = {
useHtml: await readFile(
path.join(groupDir, moduleDirName, 'index.html'),
'utf-8',
).catch(() => undefined),
entryFilePath,
outDir: path.join(
buildDir,
path.basename(groupDir),
moduleDirName,
),
}
return [playgroundKey, playgroundExample]
},
),
)
const validEntries = allEntries.filter(
([_, playgroundExample]) => playgroundExample !== undefined,
)
return [path.basename(groupDir), Object.fromEntries(validEntries)]
}),
).then((entries) =>
Object.fromEntries(
// and then filter it out.
entries.filter((entry) => entry[1] !== undefined),
),
)
// Collect all entry files
const entryPoints = Object.values(groups)
.flatMap((group) => Object.values(group))
.map((module) => module.entryFilePath)
// Collect all output directories
const outModules: PlaygroundExample[] = Object.values(groups).flatMap(
(group) => Object.values(group),
)
// Render home page contents
const homeHtml = (() => {
const sheet = new ServerStyleSheet()
try {
const html = renderToStaticMarkup(
sheet.collectStyles(
React.createElement(PlaygroundPage, {
groups: mapValues(groups, (group) => Object.keys(group)),
}),
),
)
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement();
sheet.seal()
return {
head: styleTags,
html,
}
} catch (error) {
// handle error
console.error(error)
sheet.seal()
process.exit(1)
}
})()
const esbuildConfig: BuildOptions = {
entryPoints,
bundle: true,
sourcemap: true,
outdir: buildDir,
target: ['firefox88'],
loader: {
'.png': 'file',
'.glb': 'file',
'.gltf': 'file',
'.mp3': 'file',
'.ogg': 'file',
'.svg': 'dataurl',
},
define: {
...definedGlobals,
'window.__IS_VISUAL_REGRESSION_TESTING': JSON.stringify(
options.isVisualRegressionTesting,
),
'process.env.BUILT_FOR_PLAYGROUND': JSON.stringify('true'),
},
banner: liveReload?.esbuildBanner,
// watch: liveReload?.esbuildWatch && {
// onRebuild(error, result) {
// esbuildWatchStop = result?.stop ?? esbuildWatchStop
// liveReload?.esbuildWatch.onRebuild?.(error, result)
// },
// },
plugins: [
{
name: 'watch playground assets',
setup(build) {
build.onStart(() => {})
build.onLoad(
{
filter: /index\.tsx?$/,
},
async (loadFile) => {
const indexHtmlPath = loadFile.path.replace(
/index\.tsx?$/,
'index.html',
)
const relToSrc = path.relative(srcDir, indexHtmlPath)
const isInSrcFolder = !relToSrc.startsWith('..')
if (isInSrcFolder) {
const newHtml = await readFile(indexHtmlPath, 'utf-8').catch(
() => undefined,
)
if (newHtml) {
await writeFile(
path.resolve(buildDir, relToSrc),
newHtml.replace(
/<\/body>/,
`<script src="${path.join(
'/',
relToSrc,
'../index.js',
)}"></script></body>`,
),
).catch(
wrapCatch(
`loading index.tsx creates corresponding index.html for ${relToSrc}`,
),
)
}
return {
watchFiles: [indexHtmlPath],
}
}
},
)
},
},
],
}
const ctx = await esbuild.context(esbuildConfig)
if (liveReload) {
await ctx.watch()
} else {
await ctx.rebuild()
}
// Read index.html template
const index = await readFile(
path.join(__dirname, 'index.html'),
'utf8',
).catch(wrapCatch('reading index.html template'))
await Promise.all([
// Write home page
writeFile(
path.join(buildDir, 'index.html'),
index
.replace(/<\/head>/, `${homeHtml.head}<\/head>`)
.replace(/<body>/, `<body>${homeHtml.html}`),
'utf-8',
).catch(wrapCatch('writing build index.html')),
// Write module pages
...outModules.map((outModule) =>
writeFile(
path.join(outModule.outDir, 'index.html'),
// Insert the script
(outModule.useHtml ?? index).replace(
/<\/body>/,
`<script src="${path.join(
'/',
path.relative(buildDir, outModule.outDir),
'index.js',
)}"></script></body>`,
),
'utf-8',
).catch(
wrapCatch(
`writing index.html for ${path.relative(buildDir, outModule.outDir)}`,
),
),
),
])
// Only start dev server in serve, otherwise just run build and that's it
if (!options.serve) {
await ctx.dispose()
return
}
const {serve} = options
// We start ESBuild serve with no build config because it doesn't need to build
// anything, we are already using ESBuild watch.
/** See https://esbuild.github.io/api/#serve-return-values */
const esbuildServe = await ctx.serve({servedir: buildDir})
const proxyServer = createProxyServer(liveReload?.handleRequest, {
hostname: '0.0.0.0',
port: esbuildServe.port,
})
// const proxyForceExit = createServerForceClose(proxyServer)
const portTries = serve.findAvailablePort ? 10 : 1
const portChosen = await tryMultiplePorts(defaultPort, portTries, proxyServer)
const hostedAt = `http://localhost:${portChosen}`
console.log('Playground running at', hostedAt)
if (serve.openBrowser) {
setTimeout(() => {
if (!liveReload?.hasOpenConnections()) openForOS(hostedAt)
}, 1000)
}
// return {
// async stop() {
// esbuildWatchStop?.()
// await proxyForceExit()
// },
// }
}
function wrapCatch(message: string) {
return (err: any) => {
return Promise.reject(`Rejected "${message}":\n ${err.toString()}`)
}
}
const dev = process.argv.find((arg) => ['--dev', '-d'].includes(arg)) != null
const serve =
process.argv.find((arg) => ['--serve'].includes(arg)) != null || undefined
const isCI = Boolean(process.env.CI)
start({
dev: !isCI && dev,
isVisualRegressionTesting: isCI,
serve: serve && {
findAvailablePort: !isCI,
// If not in CI, try to spawn a browser
openBrowser: !isCI,
// waitBeforeStartingServer: current?.stop(),
},
}).then(
() => {},
(err) => {
console.error(err)
process.exit(1)
},
)

View file

@ -1,60 +0,0 @@
import type esbuild from 'esbuild'
import type {IncomingMessage, ServerResponse} from 'http'
export function createEsbuildLiveReloadTools(): {
handleRequest(req: IncomingMessage, res: ServerResponse): boolean
hasOpenConnections(): boolean
esbuildBanner: esbuild.BuildOptions['banner']
} {
const openResponses = new Set<ServerResponse>()
return {
handleRequest(req, res) {
// If special /esbuild url requested, subscribe clients to changes
// if (req.url === '/esbuild') {
// res.writeHead(200, {
// 'Content-Type': 'text/event-stream',
// 'Cache-Control': 'no-cache',
// Connection: 'keep-alive',
// })
// res.write('data: open\n\n')
// openResponses.add(res)
// res.on('close', () => openResponses.delete(res))
// return true // handled
// }
return false
},
hasOpenConnections() {
return openResponses.size > 0
},
esbuildBanner: {
// Below uses function toString to insert raw source code of the function into the JS source.
// This is being used so we can at least get a few type completions, but please understand that
// you cannot reference any non-global browser values from within the function.
js: `;(${function liveReloadClientSetup() {
console.log('%cLive reload enabled', 'color: gray')
// from packages/playground/devEnv/createEsbuildLiveReloadTools.ts
function connect() {
if (window.parent !== window) {
console.log(
'%cLive reload disabled for iframed content',
'color: gray',
)
}
try {
const es = new EventSource('/esbuild')
es.addEventListener('change', () => {
console.log('%cLive reload triggered', 'color: gray')
window.location.reload()
})
} catch (err) {
attemptConnect()
}
}
function attemptConnect() {
setTimeout(() => connect(), 1000)
}
attemptConnect()
}.toString()})();`,
},
}
}

View file

@ -1,34 +0,0 @@
import type {IncomingMessage, ServerResponse} from 'http'
import {createServer, request} from 'http'
// See example from https://esbuild.github.io/api/#customizing-server-behavior
export function createProxyServer(
handleRequest:
| ((req: IncomingMessage, res: ServerResponse) => boolean)
| undefined,
target: {hostname: string; port: number},
) {
return createServer((req, res) => {
const {url, method, headers} = req
if (handleRequest?.(req, res)) {
return
}
// Otherwise forward requests to target (e.g. ESBuild server)
req.pipe(
request(
{
...target,
path: url,
method,
headers,
},
(prxRes) => {
res.writeHead(prxRes.statusCode!, prxRes.headers)
prxRes.pipe(res, {end: true})
},
),
{end: true},
)
})
}

View file

@ -1,60 +0,0 @@
import React from 'react'
import styled, {StyleSheetManager} from 'styled-components'
import {ItemSectionWithPreviews} from './ItemSectionWithPreviews'
import {PlaygroundHeader} from './PlaygroundHeader'
const HomeContainer = styled.div`
position: fixed;
inset: 0;
background: #1b1c1e;
overflow: auto;
`
const ContentContainer = styled.div`
padding: 0 5rem;
@media screen and (max-width: 920px) {
padding: 0 2rem;
}
`
const version = require('../../../../theatre/studio/package.json').version
const PageTitleH1 = styled.h1`
padding: 1rem 0;
`
export const PlaygroundPage = ({
groups,
}: {
groups: {[groupName: string]: string[]}
}) => (
<StyleSheetManager disableVendorPrefixes>
<HomeContainer>
<PlaygroundHeader
version={{
displayText: version,
}}
links={[
{
label: 'Docs',
href: 'https://www.theatrejs.com/docs/latest',
},
{
label: 'Github',
href: 'https://github.com/theatre-js/theatre',
},
]}
/>
<ContentContainer>
<PageTitleH1>Playground</PageTitleH1>
{Object.entries(groups).map(([groupName, modules]) => (
<ItemSectionWithPreviews
key={`group-${groupName}`}
groupName={groupName}
modules={modules}
/>
))}
</ContentContainer>
</HomeContainer>
</StyleSheetManager>
)

View file

@ -1,17 +0,0 @@
import {spawn} from 'child_process'
export function openForOS(hostedAt: string) {
const open = {
darwin: ['open', '-a', 'Google Chrome'],
linux: ['xdg-open'],
win32: ['cmd', '/c', 'start'],
}
const platform = process.platform as keyof typeof open
if (open[platform]) {
spawn(open[platform][0], [...open[platform].slice(1), hostedAt])
} else {
console.error(
`Failed to open (${hostedAt}) for unconfigured platform (${platform})`,
)
}
}

View file

@ -68,7 +68,7 @@ const config: PlaywrightTestConfig = {
TODO 👆 TODO 👆
*/ */
webServer: { webServer: {
command: 'yarn run serve:ci', command: 'yarn run serve:ci --port 8080',
port: 8080, port: 8080,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
}, },

View file

@ -1,47 +0,0 @@
import type {Server} from 'net'
export async function tryMultiplePorts(
port: number,
tries: number,
server: Server,
): Promise<number> {
let portToTry = port
let firstError = null
let lastError = null
while (portToTry < port + tries) {
try {
await new Promise((res, rej) => {
const onListening = () => (rm(), res(true))
const onError = () => (rm(), rej())
const rm = () => {
server.off('error', onError)
server.off('listening', onListening)
}
server
.listen(portToTry)
.on('listening', onListening)
.on('error', onError)
})
firstError = null
lastError = null
break // found a working port
} catch (err) {
if (!firstError) firstError = err
lastError = err
portToTry += 1
}
}
if (firstError) {
console.error(firstError)
console.error(lastError)
throw new Error(
`Failed to find port starting at ${port} with ${tries} tries.`,
)
}
return portToTry
}

View file

@ -8,9 +8,9 @@
"dist/**/*" "dist/**/*"
], ],
"scripts": { "scripts": {
"serve": "node -r esbuild-register devEnv/build.ts --serve --dev", "serve": "vite",
"serve:ci": "node -r esbuild-register devEnv/build.ts --serve", "serve:ci": "vite build && vite preview",
"build": "node -r esbuild-register devEnv/build.ts", "build": "vite build --force",
"build:static": "echo 'building for vercel' && yarn run build", "build:static": "echo 'building for vercel' && yarn run build",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "playwright test --config=devEnv/playwright.config.ts", "test": "playwright test --config=devEnv/playwright.config.ts",
@ -22,6 +22,7 @@
"@playwright/test": "^1.29.1", "@playwright/test": "^1.29.1",
"@react-three/drei": "^7.2.2", "@react-three/drei": "^7.2.2",
"@react-three/fiber": "^7.0.6", "@react-three/fiber": "^7.0.6",
"@rollup/plugin-virtual": "^3.0.1",
"@theatre/core": "workspace:*", "@theatre/core": "workspace:*",
"@theatre/r3f": "workspace:*", "@theatre/r3f": "workspace:*",
"@theatre/studio": "workspace:*", "@theatre/studio": "workspace:*",
@ -29,9 +30,19 @@
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/node": "^15.6.2", "@types/node": "^15.6.2",
"@types/react": "^17.0.9", "@types/react": "^17.0.9",
"@vitejs/plugin-react": "^4.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"esbuild": "^0.17.6", "esbuild": "^0.17.6",
"esbuild-register": "^3.4.2", "esbuild-register": "^3.4.2",
"parcel": "^2.9.3",
"three": "^0.130.1", "three": "^0.130.1",
"typescript": "^4.4.2" "typescript": "^4.4.2",
"vite": "^4.3.9",
"vite-plugin-commonjs": "^0.8.0",
"vite-plugin-html-template": "^1.2.0",
"vite-plugin-mpa": "^1.2.0"
},
"dependencies": {
"@originjs/vite-plugin-commonjs": "^1.0.3"
} }
} }

View file

@ -11,7 +11,7 @@ export const ItemSectionWithPreviews = (props: {
<SectionHeader>{groupName}</SectionHeader> <SectionHeader>{groupName}</SectionHeader>
<ItemListContainer> <ItemListContainer>
{modules.map((moduleName) => { {modules.map((moduleName) => {
const href = `/${groupName}/${moduleName}` const href = `/${groupName}/${moduleName}/`
return ( return (
<ItemContainer key={`li-${moduleName}`}> <ItemContainer key={`li-${moduleName}`}>
<ItemLink href={href}> <ItemLink href={href}>

View file

@ -0,0 +1,64 @@
import React from 'react'
import styled, {StyleSheetManager} from 'styled-components'
import {ItemSectionWithPreviews} from './ItemSectionWithPreviews'
import {PlaygroundHeader} from './PlaygroundHeader'
// @ts-ignore
import {version} from '../../../../theatre/studio/package.json'
const HomeContainer = styled.div`
position: fixed;
inset: 0;
background: #1b1c1e;
overflow: auto;
`
const ContentContainer = styled.div`
padding: 0 5rem;
@media screen and (max-width: 920px) {
padding: 0 2rem;
}
`
// const {version} = require('')
const PageTitleH1 = styled.h1`
padding: 1rem 0;
`
export const PlaygroundPage = ({
groups,
}: {
groups: {[groupName: string]: string[]}
}) => {
return (
<StyleSheetManager disableVendorPrefixes>
<HomeContainer>
<PlaygroundHeader
version={{
displayText: version,
}}
links={[
{
label: 'Docs',
href: 'https://www.theatrejs.com/docs/latest',
},
{
label: 'Github',
href: 'https://github.com/theatre-js/theatre',
},
]}
/>
<ContentContainer>
<PageTitleH1>Playground</PageTitleH1>
{Object.entries(groups).map(([groupName, modules]) => (
<ItemSectionWithPreviews
key={`group-${groupName}`}
groupName={groupName}
modules={modules}
/>
))}
</ContentContainer>
</HomeContainer>
</StyleSheetManager>
)
}

View file

@ -2,8 +2,10 @@
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playground Theatre.js</title> <title>Playground Theatre.js</title>
<script type="module" src="./index.tsx"></script>
<style> <style>
body { body {
margin: 0; margin: 0;

View file

@ -0,0 +1,22 @@
import {PlaygroundPage} from './home/PlaygroundPage'
import ReactDom from 'react-dom'
import React from 'react'
// like [{'./shared/hello/index.html': () => import('./shared/hello/index.html')}]
const modules: Record<string, () => Promise<unknown>> = (
import.meta as any
).glob('./(shared|personal|tests)/*/index.html')
const groups = (Object.keys(modules) as string[]).reduce((acc, path) => {
const [_, groupName, moduleName] = path.split('/')
if (!acc[groupName]) {
acc[groupName] = []
}
acc[groupName].push(moduleName)
return acc
}, {} as {[groupName: string]: string[]})
ReactDom.render(
<PlaygroundPage groups={groups} />,
document.getElementById('root'),
)

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Theatre.js Playground</title>
<script src="./index.tsx" type="module"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -15,5 +15,10 @@
{"path": "../r3f"}, {"path": "../r3f"},
{"path": "../theatric"} {"path": "../theatric"}
], ],
"include": ["./src/**/*", "./src/**/*.json", "./devEnv/**/*"] "include": [
"./src/**/*",
"./src/**/*.json",
"./devEnv/**/*",
"./vite.config.ts"
]
} }

View file

@ -0,0 +1,93 @@
import {defineConfig} from 'vite'
// import react from '@vitejs/plugin-react'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
// import {mapValues} from 'lodash-es'
// import {PlaygroundPage} from './devEnv/home/PlaygroundPage'
import fg from 'fast-glob'
import {getAliasesFromTsConfigForRollup} from '../../devEnv/getAliasesFromTsConfig'
import {definedGlobals} from '../../theatre/devEnv/definedGlobals'
import devCommonJS from 'vite-plugin-commonjs'
// import {viteCommonjs as productionCommonJS} from '@originjs/vite-plugin-commonjs'
// import mpa from 'vite-plugin-mpa'
// import htmlTemplatePlugin from 'vite-plugin-html-template'
const fromPlaygroundDir = (folder: string) => path.resolve(__dirname, folder)
// const buildDir = playgroundDir('build')
const srcDir = fromPlaygroundDir('src')
const sharedDir = fromPlaygroundDir('src/shared')
const personalDir = fromPlaygroundDir('src/personal')
const testDir = fromPlaygroundDir('src/tests')
// https://vitejs.dev/config/
const config = defineConfig(async ({command}) => {
const dev = command === 'serve'
console.log('dev', dev)
const groups = {
shared: await fg(path.join(sharedDir, '*/index.html')),
personal: await fg(path.join(personalDir, '*/index.html')),
test: await fg(path.join(testDir, '*/index.html')),
}
const rollupInputs = (() => {
// eg ['path/to/src/group/playground/index.html']
const paths = ([] as string[]).concat(...Object.values(groups))
// eg ['group/playground']
const names = paths.map((entry) => {
// convert "/path/to/src/group/playground/index.html" to "group/playground"
const relativePath = path.relative(srcDir, entry)
const entryName = relativePath.replace(/\/index\.html$/, '')
return entryName
})
// eg { 'group/playground': 'path/to/src/group/playground/index.html' }
return Object.fromEntries(names.map((name, index) => [name, paths[index]]))
})()
return {
root: srcDir,
plugins: [
react(),
dev ? devCommonJS() : /*productionCommonJS()*/ undefined,
],
appType: 'mpa',
server: {
// base: '/playground/',
},
assetsInclude: ['**/*.gltf', '**/*.glb'],
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': 'false',
},
optimizeDeps: {
exclude: dev ? ['@theatre/core', '@theatre/studio'] : [],
// include: !dev ? ['@theatre/core', '@theatre/studio'] : [],
// needsInterop: ['@theatre/core', '@theatre/studio'],
},
build: {
outDir: '../build',
minify: false,
sourcemap: true,
rollupOptions: {
input: {
...rollupInputs,
main: fromPlaygroundDir('src/index.html'),
},
},
},
}
})
export default config

View file

@ -68,3 +68,7 @@ declare module 'lodash-webpack-plugin'
declare module 'webpack-bundle-analyzer' declare module 'webpack-bundle-analyzer'
declare module 'merge-deep' declare module 'merge-deep'
declare module 'exec-loader!./commitHash' declare module 'exec-loader!./commitHash'
declare module 'blob-compare' {
const compare: (left: File | Blob, right: File | Blob) => Promise<boolean>
export default compare
}

View file

@ -33,11 +33,7 @@ const DEFAULT_PERSISTENCE_KEY = 'theatre-0.4'
export type CoreExports = typeof _coreExports export type CoreExports = typeof _coreExports
const UIConstructorModule = const UIConstructorModule =
typeof window !== 'undefined' ? require('./UI') : null typeof window !== 'undefined' ? import('./UI').then((M) => M.default) : null
// this package has a reference to `window` that breaks SSR, so we require it conditionally
const blobCompare =
typeof window !== 'undefined' ? require('blob-compare') : null
const STUDIO_NOT_INITIALIZED_MESSAGE = `You seem to have imported '@theatre/studio' but haven't initialized it. You can initialize the studio by: const STUDIO_NOT_INITIALIZED_MESSAGE = `You seem to have imported '@theatre/studio' but haven't initialized it. You can initialize the studio by:
\`\`\` \`\`\`
@ -68,7 +64,16 @@ studio.initialize()
` `
export class Studio { export class Studio {
readonly ui!: UI protected _ui: UI | null = null
// this._uiInitDeferred.promise will resolve once this._ui is set
private _uiInitDeferred = defer()
get ui() {
if (!this._ui) {
debugger
throw new Error(`Studio.ui called before UI is initialized`)
}
return this._ui
}
readonly publicApi: IStudio readonly publicApi: IStudio
readonly address: {studioId: string} readonly address: {studioId: string}
readonly _projectsProxy: PointerProxy<Record<ProjectId, Project>> = readonly _projectsProxy: PointerProxy<Record<ProjectId, Project>> =
@ -127,7 +132,14 @@ export class Studio {
// initialize UI if we're in the browser // initialize UI if we're in the browser
if (process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') { if (process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') {
this.ui = new UIConstructorModule.default(this) UIConstructorModule!
.then((M) => {
this._ui = new M(this)
this._uiInitDeferred.resolve(null)
})
.catch((error) => {
console.error(`Failed initializing the UI at @theatre/studio.`, error)
})
} }
this._attachToIncomingProjects() this._attachToIncomingProjects()
@ -205,9 +217,11 @@ export class Studio {
this._initializedDeferred.resolve() this._initializedDeferred.resolve()
if (process.env.NODE_ENV !== 'test' && this.ui) { if (process.env.NODE_ENV !== 'test') {
this._uiInitDeferred.promise.then(() => {
this.ui.render() this.ui.render()
checkForUpdates() checkForUpdates()
})
} }
} }
@ -468,8 +482,10 @@ export class Studio {
} }
if (existingAsset) { if (existingAsset) {
const blobCompare = (await import('blob-compare')).default
// @ts-ignore // @ts-ignore
sameSame = await blobCompare!.isEqual(asset, existingAsset) sameSame = await blobCompare.isEqual(asset, existingAsset)
// if same same, we do nothing // if same same, we do nothing
if (sameSame) { if (sameSame) {

3054
yarn.lock

File diff suppressed because it is too large Load diff