Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(playground): add monaco-editor #490

Merged
merged 11 commits into from
Sep 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
@@ -11,8 +11,8 @@
"**/brisa/client/*",
"**/brisa/jsx-runtime/*",
"**/brisa/jsx-dev-runtime/*",
"**/docs/.vitepress/cache/*",
"**/docs/.vitepress/dist/*"
"**/www/src/public/*",
"**/www/.vercel/*"
]
},
"formatter": {
Binary file modified bun.lockb
Binary file not shown.
8 changes: 3 additions & 5 deletions packages/brisa/package.json
Original file line number Diff line number Diff line change
@@ -131,12 +131,10 @@
"devDependencies": {
"@happy-dom/global-registrator": "15.7.3",
"@types/bun": "1.1.8",
"brisa": "0.0.208-canary.1",
"brisa": "latest",
"@types/mime-types": "2.1.4",
"mime-types": "2.1.35"
},
"peerDependencies": {
"typescript": "5.5.4"
"mime-types": "2.1.35",
"typescript": "5.6.2"
},
"packageManager": "bun@1.1.28",
"engines": {
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@ const pageWebComponents = {

const i18nCode = 3072;
const brisaSize = 5743; // TODO: Reduce this size :/
const webComponents = 1130;
const webComponents = 1144;
const unsuspenseSize = 217;
const rpcSize = 2468; // TODO: Reduce this size
const rpcSize = 2467; // TODO: Reduce this size
const lazyRPCSize = 4171; // TODO: Reduce this size
// lazyRPC is loaded after user interaction (action, link),
// so it's not included in the initial size
2 changes: 0 additions & 2 deletions packages/create-brisa/create-brisa.cjs
Original file line number Diff line number Diff line change
@@ -57,8 +57,6 @@ function createProject(PROJECT_NAME) {
},
devDependencies: {
'@types/bun': 'latest',
},
peerDependencies: {
typescript: 'latest',
},
};
2 changes: 1 addition & 1 deletion packages/create-brisa/package.json
Original file line number Diff line number Diff line change
@@ -28,6 +28,6 @@
"pnpm": "please-use-bun"
},
"devDependencies": {
"brisa": "0.0.208-canary.1"
"brisa": "latest"
}
}
3 changes: 2 additions & 1 deletion packages/www/.gitignore
Original file line number Diff line number Diff line change
@@ -2,4 +2,5 @@ build
node_modules
out
.vercel
.DS_Store
.DS_Store
src/public/monaco-editor/*
2 changes: 1 addition & 1 deletion packages/www/package.json
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@
"search-engine-wc": "0.2.2",
"shikiji-transformers": "0.10.2",
"slugify": "1.6.6",
"@swc/wasm-web": "1.7.26"
"@swc/wasm-web": "1.7.28"
},
"devDependencies": {
"@types/bun": "1.1.10",
38 changes: 38 additions & 0 deletions packages/www/src/helpers/monaco-extra-libs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import path from 'node:path';
import fs from 'node:fs';
import { fileSystemRouter } from 'brisa/server';

/**
* TODO: It's not working yet ... We are missing some configuration after that
*/
export default function getMonacoEditorExtraLibs() {
const fileExtensions = ['.d.ts', '.json'];
const root = path.resolve(path.join(process.cwd(), '..', '..'));
const nodeModules = path.join(root, 'node_modules');
const brisaDep = fileSystemRouter({
dir: path.join(nodeModules, 'brisa'),
fileExtensions,
});
const cssType = fileSystemRouter({
dir: path.join(nodeModules, 'csstype'),
fileExtensions,
});
let extraLibs = '';

for (const [pathname] of brisaDep.routes) {
if (pathname.includes('node_modules') || pathname.includes('src')) continue;
const route = brisaDep.match(pathname);
const filePath = route.filePath.replace(root, '');
const fileRaw = fs.readFileSync(route.filePath, 'utf-8');
extraLibs += `monaco.languages.typescript.typescriptDefaults.addExtraLib(\`${fileRaw}\`, 'file://${filePath}');`;
}

for (const [pathname] of cssType.routes) {
const route = cssType.match(pathname);
const filePath = route.filePath.replace(root, '');
const fileRaw = fs.readFileSync(route.filePath, 'utf-8');
extraLibs += `monaco.languages.typescript.typescriptDefaults.addExtraLib(\`${fileRaw}\`, 'file://${filePath}');`;
}

return extraLibs;
}
30 changes: 28 additions & 2 deletions packages/www/src/layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Nav from '@/components/navigation';
import Footer from '@/components/footer';
import { dangerHTML } from 'brisa';
import { dangerHTML, type RequestContext } from 'brisa';

const meta = {
title: 'Brisa - The Web Platform Framework',
@@ -22,7 +22,14 @@ const speculationrules = {
],
};

export default function Layout({ children }: { children: JSX.Element }) {
export default function Layout(
{ children }: { children: JSX.Element },
{ route }: RequestContext,
) {
if (route.pathname === '/playground/preview') {
return <PreviewLayout>{children}</PreviewLayout>;
}

return (
<html lang="en">
<head>
@@ -88,6 +95,25 @@ export default function Layout({ children }: { children: JSX.Element }) {
);
}

function PreviewLayout({ children }: { children: JSX.Element }) {
return (
<html lang="en">
<head>
<script type="importmap">
{dangerHTML(`
{
"imports": {
"brisa/client": "https://unpkg.com/brisa@latest/client-simplified/index.js"
}
}
`)}
</script>
</head>
<body style={{ backgroundColor: 'white' }}>{children}</body>
</html>
);
}

function initTheme() {
if (document.body.classList.length) return;

18 changes: 0 additions & 18 deletions packages/www/src/pages/playground.tsx

This file was deleted.

105 changes: 105 additions & 0 deletions packages/www/src/pages/playground/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { dangerHTML } from 'brisa';
import getMonacoEditorExtraLibs from '@/helpers/monaco-extra-libs';

const defaultValue = `// src/web-components/wc-counter.tsx
import type { WebContext } from 'brisa';

export default function Counter({ name }: { name: string }, { state }: WebContext) {
const count = state(0);

return (
<p>
<button onClick={() => count.value++}>+</button>
<span> {name} {count.value} </span>
<button onClick={() => count.value--}>-</button>
</p>
)
}`;

export default function Playground() {
return (
<>
<main style={{ paddingTop: '100px' }}>
<play-ground skipSSR defaultValue={defaultValue}>
<div
slot="code-editor"
style={{
height: '500px',
width: '100%',
border: '1px solid var(--color-primary)',
}}
id="code-editor"
>
<script type="module">
{dangerHTML(`
import * as monaco from 'https://esm.sh/monaco-editor';
import tsWorker from 'https://esm.sh/monaco-editor/esm/vs/language/typescript/ts.worker?worker';

window.MonacoEnvironment = {
getWorker(_, label) {
return new tsWorker();
}
};

const modelUri = monaco.Uri.file("wc-counter.tsx")
const existingModel = monaco.editor.getModels().find(m => m.uri.toString() === modelUri.toString());
const codeModel = existingModel ?? monaco.editor.createModel(\`${defaultValue}\`, "typescript", modelUri);

monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
jsx: "react",
target: monaco.languages.typescript.ScriptTarget.ES2020,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.Classic,
allowNonTsExtensions: true
});

${getMonacoEditorExtraLibs()}

monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false
});
const preview = document.querySelector('#preview-iframe');
const editor = monaco.editor.create(document.querySelector('#code-editor'), {
theme: document.body.classList.contains('dark') ? "vs-dark" : "vs-light",
});
editor.setModel(codeModel);
editor.onDidChangeModelContent((e) => {
preview.contentWindow.postMessage({ code: editor.getValue() }, '*');
});
window._xm = "native";
window.changeTheme = monaco.editor.setTheme.bind(monaco.editor);
`)}
</script>
</div>
<iframe
slot="preview-iframe"
id="preview-iframe"
style={{
width: '100%',
border: '1px solid var(--color-primary)',
height: '500px',
color: 'var(--color-primary)',
}}
src="/playground/preview"
/>
</play-ground>
</main>
</>
);
}

export function Head() {
return (
<>
<link
rel="preload"
href="https://esm.sh/monaco-editor/min/vs/editor/editor.main.css"
as="style"
/>
<link
rel="stylesheet"
href="https://esm.sh/monaco-editor/min/vs/editor/editor.main.css"
/>
</>
);
}
3 changes: 3 additions & 0 deletions packages/www/src/pages/playground/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Playground() {
return <play-ground-preview skipSSR />;
}
2,370 changes: 1,828 additions & 542 deletions packages/www/src/public/content.json

Large diffs are not rendered by default.

594 changes: 0 additions & 594 deletions packages/www/src/public/modules/index.js

This file was deleted.

2 changes: 1 addition & 1 deletion packages/www/src/public/styles/nav.css
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
background-color: var(--color-white);
position: fixed;
color: var(--color-primary);
z-index: 2;
z-index: 6;
padding: 1rem 2rem;
width: 100vw;
border-bottom: 1px solid var(--color-border);
7 changes: 6 additions & 1 deletion packages/www/src/web-components/change-theme.tsx
Original file line number Diff line number Diff line change
@@ -13,6 +13,10 @@ export default function ChangeTheme({}, { state, css }: WebContext) {
document.body.classList.remove('dark');
document.body.classList.remove('light');
document.body.classList.add(theme);
if ('changeTheme' in window) {
// @ts-ignore
window.changeTheme(theme === 'dark' ? 'vs-dark' : 'vs-light');
}
isDark.value = !isDark.value;
}

@@ -43,7 +47,8 @@ export default function ChangeTheme({}, { state, css }: WebContext) {
aria-label="Change theme"
onClick={() => {
'startViewTransition' in document
? document.startViewTransition(changeColor)
? // @ts-ignore
document.startViewTransition(changeColor)
: changeColor();
}}
>
73 changes: 73 additions & 0 deletions packages/www/src/web-components/play-ground-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { dangerHTML, type WebContext } from 'brisa';
import { compileWC } from 'brisa/compiler';

const source = 'brisa-playground-preview';
const workerCode = `import initSwc, { transformSync } from "https://esm.sh/@swc/wasm-web@1.7.26/wasm.js";
initSwc().then(() => self.postMessage({ type: "ready" }));
self.addEventListener("message", async (event) => {
const { code } = event.data;
const result = transformSync(code, {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
dynamicImport: true,
},
transform: {
react: {
runtime: "automatic",
},
},
target: "es2020",
},
});
self.postMessage(result);
});`;

export default async function PlayGroundPreview(
{},
{ state, cleanup }: WebContext,
) {
const ready = state<boolean>(false);
const workerBlob = new Blob([workerCode], { type: 'application/javascript' });
const url = URL.createObjectURL(workerBlob);
const worker = new Worker(url, { type: 'module', name: 'SWC Worker' });
const selector = state<string>('playground-result');
let count = 0;

async function onUpdateCode(e: MessageEvent) {
if (e.data?.type === 'ready') {
ready.value = true;
window.parent.postMessage({ source, ready: true });
return;
}
const compiledCode = compileWC(e.data.code);

window.parent.postMessage({ source, code: compiledCode });

const codeBlob = new Blob([compiledCode], {
type: 'application/javascript',
});
const codeUrl = URL.createObjectURL(codeBlob);
const el = (await import(codeUrl)).default;
const newSelector = `playground-result-${++count}`;
customElements.define(newSelector, el);
selector.value = newSelector;
}

function onReceiveUncompiledCode(e: MessageEvent) {
if (typeof e.data?.code !== 'string') return;
worker.postMessage({ code: e.data.code });
}

worker.addEventListener('message', onUpdateCode);
window.addEventListener('message', onReceiveUncompiledCode);
cleanup(() => {
worker.removeEventListener('message', onUpdateCode);
window.removeEventListener('message', onReceiveUncompiledCode);
});

if (!ready.value) return null;

return dangerHTML(`<${selector.value}></${selector.value}>`);
}
118 changes: 23 additions & 95 deletions packages/www/src/web-components/play-ground.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,31 @@
import type { WebContext } from 'brisa';
import { compileWC } from 'brisa/compiler';

const defaultValue = ` export default function Counter({ name }: any, { state }: any) {
const count = state(0);
return (
<p>
<button onClick={() => count.value++}>+</button>
<span> {name} {count.value} </span>
<button onClick={() => count.value--}>-</button>
</p>
)
}`;

const workerCode = `import initSwc, { transformSync } from "https://unpkg.com/@swc/wasm-web@1.7.26/wasm.js";
initSwc().then(() => self.postMessage({ type: "ready" }));
self.addEventListener("message", async (event) => {
const { code } = event.data;
const result = transformSync(code, {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
dynamicImport: true,
},
transform: {
react: {
runtime: "automatic",
},
},
target: "es2020",
},
});
self.postMessage(result.code);
});`;

export default async function PlayGround(
{},
{ state, cleanup, css, self }: WebContext,
{ defaultValue }: { defaultValue: string },
{ state, css, cleanup, onMount, self }: WebContext,
) {
const ready = state<boolean>(false);
const workerBlob = new Blob([workerCode], { type: 'application/javascript' });
const url = URL.createObjectURL(workerBlob);
const worker = new Worker(url, { type: 'module', name: 'SWC Worker' });
const code = state<string>('');
let count = 0;
const preview: HTMLIFrameElement = self.querySelector('#preview-iframe')!;

async function onUpdateCode(e) {
if (e.data?.type === 'ready') {
ready.value = true;
worker.postMessage({ code: defaultValue });
return;
function onReceiveCompiledCode(e: MessageEvent) {
if (e.data.source !== 'brisa-playground-preview') return;
if (e.data.ready) sendDefaultCode();
if (typeof e.data.code === 'string') {
code.value = e.data.code;
}
const compiledCode = compileWC(e.data);
code.value = compiledCode;
const js = new Blob(
[
compiledCode.replace(
'brisa/client',
'https://unpkg.com/brisa@latest/client-simplified/index.js',
),
],
{ type: 'application/javascript' },
);
const element = (await import(URL.createObjectURL(js))).default;

// Note: this is a hack that will be improved in the future
const selector = `playground-result-${++count}`;
}

// remove the previous element
customElements.define(selector, element);
const prev = self.shadowRoot!.querySelector('#playground-result')!;
const newEl = document.createElement(selector);
newEl.id = 'playground-result';
prev.replaceWith(newEl);
function sendDefaultCode() {
preview.contentWindow?.postMessage({ code: defaultValue });
}

worker.addEventListener('message', onUpdateCode);
cleanup(() => worker.removeEventListener('message', onUpdateCode));
onMount(() => {
window.addEventListener('message', onReceiveCompiledCode);
});

async function onInput(e: Event) {
const target = e.target as HTMLTextAreaElement;
if (!ready.value) {
console.error('Worker is not ready yet');
return;
}
worker.postMessage({ code: target.value });
}
cleanup(() => {
window.removeEventListener('message', onReceiveCompiledCode);
});

css`
.playground {
@@ -123,25 +62,14 @@ export default async function PlayGround(
return (
<section class="playground">
<div class="original-code">
<h2>Original code:</h2>
<textarea onInput={onInput}>{defaultValue}</textarea>
<h2>Code:</h2>
<slot name="code-editor" />
</div>
<div class="output">
{ready.value ? (
<>
<h2>Web Component:</h2>
<div class="wc">
{/* @ts-ignore */}
<playground-result-0 id="playground-result" />
</div>
<h2>Compiled Code:</h2>
<textarea onInput={onInput} disabled>
{code.value}
</textarea>
</>
) : (
'Compiling...'
)}
<h2>Web Component:</h2>
<slot name="preview-iframe" />
<h2>Compiled Code:</h2>
<textarea disabled>{code.value}</textarea>
</div>
</section>
);
16 changes: 16 additions & 0 deletions packages/www/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"redirects": [
{
"source": "/discord",
"destination": "https://discord.com/invite/MsE9RN3FU4"
},
{
"source": "/x",
"destination": "https://x.com/brisadotbuild"
},
{
"source": "/github",
"destination": "https://github.com/brisa-build/brisa"
}
]
}

Unchanged files with check annotations Beta

},
);
const response = await testRequest(req);
const textBuffer = gunzipSync(new Uint8Array(await response.arrayBuffer()));

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: incorrect header check

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: incorrect header check

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: incorrect header check

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: incorrect header check

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: incorrect header check

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: incorrect header check

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: incorrect header check

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24

Check failure on line 969 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: incorrect header check

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:969:24
const text = textDecoder.decode(textBuffer);
expect(response.status).toBe(200);
},
);
const response = await testRequest(req);
const textBuffer = brotliDecompressSync(

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: BrotliError

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: BrotliError

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: BrotliError

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

error: BrotliError

at /home/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: BrotliError

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: BrotliError

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24

Check failure on line 1054 in packages/brisa/src/cli/serve/serve-options.test.tsx

GitHub Actions / bun-tests (macos-latest)

error: BrotliError

at /Users/runner/work/brisa/brisa/packages/brisa/src/cli/serve/serve-options.test.tsx:1054:24
new Uint8Array(await response.arrayBuffer()),
);
const text = textDecoder.decode(textBuffer);
expect(tripleRender?.shadowRoot?.innerHTML).toBeEmpty();
window._s.set('show', true);

Check failure on line 5199 in packages/brisa/src/utils/client-build-plugin/integration.test.tsx

GitHub Actions / bun-tests (ubuntu-latest)

TypeError: undefined is not an object (evaluating 'window._s.set')

at /home/runner/work/brisa/brisa/packages/brisa/src/utils/client-build-plugin/integration.test.tsx:5199:14
const foo = tripleRender?.shadowRoot?.querySelector(
'div',