Skip to content

Commit

Permalink
Docs: Add proper playground
Browse files Browse the repository at this point in the history
Improve compilation to not insert inject calls

Add Vercel headers

Configure local development for playground

Add codemirror editor

Use rollup and static server.

Delay preview to wait for built files

chore: update to latest StyleX for playground

chore: fix build

chore: playground actually builds

chore: rebase and reinstall
  • Loading branch information
nmn committed Dec 16, 2024
1 parent c882412 commit 7512b03
Show file tree
Hide file tree
Showing 7 changed files with 423 additions and 42 deletions.
141 changes: 141 additions & 0 deletions apps/docs/components/Playground.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

import * as React from 'react';
import {useEffect, useState, useRef} from 'react';
import * as stylex from '@stylexjs/stylex';
import BrowserOnly from '@docusaurus/BrowserOnly';
import {WebContainer} from '@webcontainer/api';
import {files} from './playground-utils/files';
import {UnControlled as CodeMirror} from 'react-codemirror2';

async function wcSpawn(instance, ...args) {
console.log('Running:', args.join(' '));
const process = await instance.spawn(...args);
process.output.pipeTo(
new WritableStream({
write(data) {
console.log(data);
},
}),
);
const exitCode = await process.exit;
if (exitCode !== 0) {
console.log('Command Failed:', args.join(' '), 'with exit code', exitCode);
throw new Error('Command Failed', args.join(' '));
}

console.log('Command Successful:', args.join(' '));
return process;
}

async function makeWebcontainer() {
console.log('Booting WebContainer...');
const instance = await WebContainer.boot();
console.log('Boot successful!');

console.log('Mounting files...');
await instance.mount(files);
console.log('Mounted files!');

console.log('Installing dependencies...');
await wcSpawn(instance, 'npm', ['install']);
console.log('Installed dependencies!');

return instance;
}

export default function Playground() {
const instance = useRef(null);
const [url, setUrl] = useState(null);

const build = async () => {
const containerInstance = instance.current;
if (!containerInstance) return;

console.log('Trying to run `npm start`...');
const process = await containerInstance.spawn('npm', ['start']);
console.log('Spawned `npm start`...');
process.output.pipeTo(
new WritableStream({
write(data) {
console.log(data);
},
}),
);

console.log('Waiting for server-ready event...');
containerInstance.on('server-ready', (port, url) => {
console.log('server-ready', port, url);
// TODO: Figure out hot reloading
// TODO: Figure out how to start server *after* build
setTimeout(() => {
setUrl(url);
}, 5000);
});
};

useEffect(() => {
require('codemirror/mode/javascript/javascript');
makeWebcontainer().then((i) => {
instance.current = i;
build();
});
() => {
instance.current.unmount();
};
}, []);

return (
<div {...stylex.props(styles.container)}>
<BrowserOnly>
{() => (
<>
<CodeMirror
{...stylex.props(styles.textarea)}
options={{
mode: 'javascript',
theme: 'material-darker',
lineNumbers: true,
}}
value={files.src.directory['app.jsx'].file.contents}
/>
{url ? (
<iframe {...stylex.props(styles.textarea)} src={url} />
) : (
<div {...stylex.props(styles.textarea)}>Loading...</div>
)}
</>
)}
</BrowserOnly>
</div>
);
}

const styles = stylex.create({
container: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: stylex.firstThatWorks('calc(100dvh - 60px)', 'calc(100vh - 60px)'),
borderBottomWidth: 2,
borderBottomStyle: 'solid',
borderBottomColor: 'var(--cyan)',
},
textarea: {
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
justifyContent: 'stretch',
width: '50%',
height: '100%',
borderWidth: 0,
borderStyle: 'none',
},
});
182 changes: 182 additions & 0 deletions apps/docs/components/playground-utils/files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

export const files = {
'.babelrc.js': {
file: {
contents: `
module.exports = {
presets: ["@babel/preset-react"],
plugins: [
"transform-node-env-inline",
[
"@stylexjs/babel-plugin",
{
dev: false,
stylexSheetName: "<>",
genConditionalClasses: true,
unstable_moduleResolution: {
type: "commonJS",
rootDir: "/",
},
},
],
],
};
`,
},
},
'index.html': {
file: {
contents: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Stylex Playground</title>
<link rel="stylesheet" href="./stylex.css">
</head>
<body>
<h1>Loaded HTML from webcontainer!</h1>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
`,
},
},
'rollup.config.mjs': {
file: {
contents: `
import babel from "@rollup/plugin-babel";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import serve from "rollup-plugin-serve";
import stylex from "@stylexjs/rollup-plugin";
const extensions = [".js", ".jsx", "ts", "tsx"];
export default {
extensions,
input: "./input.js",
output: {
file: "./bundle.js",
format: "umd",
},
plugins: [
stylex({ fileName: "stylex.css" }),
babel({
babelHelpers: "bundled",
extensions,
configFile: "./.babelrc.js",
}),
resolve({ extensions }),
commonjs(),
serve({
contentBase: "./",
port: 3111,
}),
],
};
`,
},
},
'package.json': {
file: {
contents: `
{
"name": "stylex-playground",
"version": "1.0.0",
"description": "Playground using WebContainers",
"main": "index.js",
"scripts": {
"build": "babel ./input.js -o ./output.js",
"start": "rollup --config ./rollup.config.mjs --watch"
},
"dependencies": {
"@babel/cli": "latest",
"@babel/core": "latest",
"@babel/plugin-syntax-flow": "latest",
"@babel/plugin-syntax-jsx": "latest",
"@babel/plugin-syntax-typescript": "latest",
"@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.23.3",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "25.0.7",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-typescript": "^11.1.5",
"@stylexjs/babel-plugin": "0.7.5",
"@stylexjs/rollup-plugin": "0.7.5",
"@stylexjs/stylex": "0.7.5",
"babel-plugin-transform-node-env-inline": "^0.4.3",
"react": "*",
"react-dom": "*",
"rollup": "4.6.1",
"rollup-plugin-serve": "3.0.0"
}
}
`,
},
},
'input.js': {
file: {
contents: `
import * as React from "react";
import { createRoot } from 'react-dom/client';
import Card from "./src/app";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(React.createElement(Card, { em: true }, "Hello World!"));
`,
},
},
src: {
directory: {
'app.jsx': {
file: {
contents: `
import * as React from 'react';
import * as stylex from "@stylexjs/stylex";
export default function Card({ children, em = false, ...props }) {
return React.createElement(
"div",
{
...props,
...stylex.props(styles.base, em && styles.emphasise)
},
children,
);
}
const styles = stylex.create({
base: {
appearance: "none",
backgroundColor: "blue",
borderRadius: 4,
borderStyle: "none",
boxSize: "border-box",
color: "white",
marginInline: 'auto',
paddingBlock: 4,
paddingInline: 8,
width: '95%',
},
emphasise: {
transform: "rotate(-2deg)",
}
});
`,
},
},
},
},
};
5 changes: 4 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"serve": "serve ./build -c ../serve.json",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"lint": "eslint --cache \"**/*.js\" && stylelint \"**/*.css\""
Expand All @@ -21,8 +21,11 @@
"@mdx-js/react": "^1.6.22",
"@stylexjs/stylex": "0.9.3",
"@vercel/analytics": "^1.1.1",
"@webcontainer/api": "1.1.2",
"clsx": "^1.2.1",
"codemirror": "^5.65.16",
"react": "^17.0.2",
"react-codemirror2": "^7.3.0",
"react-dom": "^17.0.2",
"react-syntax-highlighter": "^15.6.1"
},
Expand Down
21 changes: 21 additions & 0 deletions apps/docs/serve.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"headers": [
{
"source": "**/**",
"headers": [
{
"key": "Cross-Origin-Embedder-Policy",
"value": "require-corp"
},
{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin"
},
{
"key": "Cross-Origin-Resource-Policy",
"value": "same-site"
}
]
}
]
}
6 changes: 6 additions & 0 deletions apps/docs/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
* work well for content-centric websites.
*/

@import 'codemirror/lib/codemirror.css';
@import 'codemirror/theme/material-darker.css';

/* You can override the default Infima variables here. */
:root {
--aa-text-color-rgb: 38, 38, 39;
Expand Down Expand Up @@ -283,3 +286,6 @@ html[data-theme='dark'] .DocSearch-Logo svg path,
html[data-theme='dark'] .DocSearch-Logo svg rect {
fill: rgb(170, 175, 186);
}
.CodeMirror {
height: 100%;
}
Loading

0 comments on commit 7512b03

Please sign in to comment.