From 2950e50c0c9c2b20bf85ec58416b338036c98b31 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Mon, 23 May 2022 15:30:16 -0500 Subject: [PATCH 01/11] refactor: repl reload, examples --- packages/docs/package.json | 2 +- .../docs/pages/examples/examples-menu.json | 24 +- .../{ => introduction}/hello-world/app.tsx | 0 .../hello-world}/entry.server.tsx | 0 .../{ => introduction}/hello-world/root.tsx | 0 .../examples/{ => reactivity}/counter/app.tsx | 0 .../counter}/entry.server.tsx | 0 .../{ => reactivity}/counter/root.tsx | 0 .../playground/{hello-world => app}/app.tsx | 0 .../{counter => app}/entry.server.tsx | 0 .../playground/{hello-world => app}/root.tsx | 0 .../docs/pages/playground/counter/app.tsx | 14 - .../docs/pages/playground/counter/root.tsx | 14 - .../playground/hello-world/entry.server.tsx | 6 - .../pages/playground/playground-menu.json | 6 - .../docs/pages/tutorial/tutorial-menu.json | 4 +- packages/docs/public/repl/index.html | 110 ------ packages/docs/public/repl/repl-server.html | 120 +++++++ .../docs/src/components/header/header.tsx | 2 +- packages/docs/src/components/page/page.tsx | 7 +- packages/docs/src/components/repl/editor.tsx | 5 +- packages/docs/src/components/repl/monaco.tsx | 119 +++++-- .../src/components/repl/repl-input-panel.tsx | 10 +- .../docs/src/components/repl/repl-options.tsx | 38 +-- .../src/components/repl/repl-output-panel.tsx | 18 +- .../src/components/repl/repl-output-update.ts | 64 ++++ .../docs/src/components/repl/repl-version.ts | 85 +++++ packages/docs/src/components/repl/repl.css | 5 +- packages/docs/src/components/repl/repl.tsx | 125 ++----- packages/docs/src/components/repl/types.ts | 25 +- .../src/components/repl/worker/context.ts | 12 +- .../repl/worker/repl-service-worker.ts | 7 +- .../components/repl/worker/request-handler.ts | 38 ++- .../src/components/repl/worker/ssr-html.ts | 44 +-- .../docs/src/components/repl/worker/update.ts | 31 +- packages/docs/src/entry.cloudflare.tsx | 12 +- .../src/layouts/examples/examples-data.ts | 13 +- .../docs/src/layouts/examples/examples.css | 12 +- .../docs/src/layouts/examples/examples.tsx | 88 +++-- .../src/layouts/playground/playground-data.ts | 6 +- .../src/layouts/playground/playground.tsx | 16 +- .../src/layouts/tutorial/tutorial-data.ts | 6 +- packages/docs/vite.config.ts | 321 +----------------- packages/docs/vite.repl-apps.ts | 266 +++++++++++++++ packages/docs/vite.repl-worker.ts | 74 ++++ yarn.lock | 14 +- 46 files changed, 982 insertions(+), 781 deletions(-) rename packages/docs/pages/examples/{ => introduction}/hello-world/app.tsx (100%) rename packages/docs/pages/examples/{counter => introduction/hello-world}/entry.server.tsx (100%) rename packages/docs/pages/examples/{ => introduction}/hello-world/root.tsx (100%) rename packages/docs/pages/examples/{ => reactivity}/counter/app.tsx (100%) rename packages/docs/pages/examples/{hello-world => reactivity/counter}/entry.server.tsx (100%) rename packages/docs/pages/examples/{ => reactivity}/counter/root.tsx (100%) rename packages/docs/pages/playground/{hello-world => app}/app.tsx (100%) rename packages/docs/pages/playground/{counter => app}/entry.server.tsx (100%) rename packages/docs/pages/playground/{hello-world => app}/root.tsx (100%) delete mode 100644 packages/docs/pages/playground/counter/app.tsx delete mode 100644 packages/docs/pages/playground/counter/root.tsx delete mode 100644 packages/docs/pages/playground/hello-world/entry.server.tsx delete mode 100644 packages/docs/pages/playground/playground-menu.json delete mode 100644 packages/docs/public/repl/index.html create mode 100644 packages/docs/public/repl/repl-server.html create mode 100644 packages/docs/src/components/repl/repl-output-update.ts create mode 100644 packages/docs/src/components/repl/repl-version.ts create mode 100644 packages/docs/vite.repl-apps.ts create mode 100644 packages/docs/vite.repl-worker.ts diff --git a/packages/docs/package.json b/packages/docs/package.json index a9a2b00436f..33994ba0367 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@builder.io/partytown": "^0.5.4", - "@builder.io/qwik": "0.0.20-5", + "@builder.io/qwik": "0.0.20-7", "@builder.io/qwik-city": "0.0.5", "@cloudflare/kv-asset-handler": "0.2.0", "@cloudflare/workers-types": "^3.10.0", diff --git a/packages/docs/pages/examples/examples-menu.json b/packages/docs/pages/examples/examples-menu.json index 85e6cc32475..014d71d3523 100644 --- a/packages/docs/pages/examples/examples-menu.json +++ b/packages/docs/pages/examples/examples-menu.json @@ -1,12 +1,24 @@ [ { - "id": "hello-world", - "title": "Hello World", - "description": "The simplest qwik app" + "id": "introduction", + "title": "Introduction", + "apps": [ + { + "id": "hello-world", + "title": "Hello World", + "description": "The simplest qwik app" + } + ] }, { - "id": "counter", - "title": "Counter", - "description": "A simple standard counter example" + "id": "reactivity", + "title": "Reactivity", + "apps": [ + { + "id": "counter", + "title": "Counter", + "description": "A simple standard counter example" + } + ] } ] diff --git a/packages/docs/pages/examples/hello-world/app.tsx b/packages/docs/pages/examples/introduction/hello-world/app.tsx similarity index 100% rename from packages/docs/pages/examples/hello-world/app.tsx rename to packages/docs/pages/examples/introduction/hello-world/app.tsx diff --git a/packages/docs/pages/examples/counter/entry.server.tsx b/packages/docs/pages/examples/introduction/hello-world/entry.server.tsx similarity index 100% rename from packages/docs/pages/examples/counter/entry.server.tsx rename to packages/docs/pages/examples/introduction/hello-world/entry.server.tsx diff --git a/packages/docs/pages/examples/hello-world/root.tsx b/packages/docs/pages/examples/introduction/hello-world/root.tsx similarity index 100% rename from packages/docs/pages/examples/hello-world/root.tsx rename to packages/docs/pages/examples/introduction/hello-world/root.tsx diff --git a/packages/docs/pages/examples/counter/app.tsx b/packages/docs/pages/examples/reactivity/counter/app.tsx similarity index 100% rename from packages/docs/pages/examples/counter/app.tsx rename to packages/docs/pages/examples/reactivity/counter/app.tsx diff --git a/packages/docs/pages/examples/hello-world/entry.server.tsx b/packages/docs/pages/examples/reactivity/counter/entry.server.tsx similarity index 100% rename from packages/docs/pages/examples/hello-world/entry.server.tsx rename to packages/docs/pages/examples/reactivity/counter/entry.server.tsx diff --git a/packages/docs/pages/examples/counter/root.tsx b/packages/docs/pages/examples/reactivity/counter/root.tsx similarity index 100% rename from packages/docs/pages/examples/counter/root.tsx rename to packages/docs/pages/examples/reactivity/counter/root.tsx diff --git a/packages/docs/pages/playground/hello-world/app.tsx b/packages/docs/pages/playground/app/app.tsx similarity index 100% rename from packages/docs/pages/playground/hello-world/app.tsx rename to packages/docs/pages/playground/app/app.tsx diff --git a/packages/docs/pages/playground/counter/entry.server.tsx b/packages/docs/pages/playground/app/entry.server.tsx similarity index 100% rename from packages/docs/pages/playground/counter/entry.server.tsx rename to packages/docs/pages/playground/app/entry.server.tsx diff --git a/packages/docs/pages/playground/hello-world/root.tsx b/packages/docs/pages/playground/app/root.tsx similarity index 100% rename from packages/docs/pages/playground/hello-world/root.tsx rename to packages/docs/pages/playground/app/root.tsx diff --git a/packages/docs/pages/playground/counter/app.tsx b/packages/docs/pages/playground/counter/app.tsx deleted file mode 100644 index 59fb94682b3..00000000000 --- a/packages/docs/pages/playground/counter/app.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { component$, useStore } from '@builder.io/qwik'; - -export const App = component$(() => { - const store = useStore({ count: 0 }); - - return ( -
-

Count: {store.count}

-

- -

-
- ); -}); diff --git a/packages/docs/pages/playground/counter/root.tsx b/packages/docs/pages/playground/counter/root.tsx deleted file mode 100644 index 72d9c413835..00000000000 --- a/packages/docs/pages/playground/counter/root.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { App } from './app'; - -export const Root = () => { - return ( - - - Counter - - - - - - ); -}; diff --git a/packages/docs/pages/playground/hello-world/entry.server.tsx b/packages/docs/pages/playground/hello-world/entry.server.tsx deleted file mode 100644 index c7fcf6bf8e6..00000000000 --- a/packages/docs/pages/playground/hello-world/entry.server.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { renderToString, RenderToStringOptions } from '@builder.io/qwik/server'; -import { Root } from './root'; - -export function render(opts: RenderToStringOptions) { - return renderToString(, opts); -} diff --git a/packages/docs/pages/playground/playground-menu.json b/packages/docs/pages/playground/playground-menu.json deleted file mode 100644 index 5c84414ca00..00000000000 --- a/packages/docs/pages/playground/playground-menu.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "id": "hello-world", - "title": "Hello World" - } -] diff --git a/packages/docs/pages/tutorial/tutorial-menu.json b/packages/docs/pages/tutorial/tutorial-menu.json index 38a2091897d..43e2f5d9107 100644 --- a/packages/docs/pages/tutorial/tutorial-menu.json +++ b/packages/docs/pages/tutorial/tutorial-menu.json @@ -2,7 +2,7 @@ { "id": "introduction", "title": "Introduction", - "tutorials": [ + "apps": [ { "id": "basics", "title": "Basics", @@ -22,7 +22,7 @@ { "id": "reactivity", "title": "Reactivity", - "tutorials": [ + "apps": [ { "id": "assignments", "title": "Assignments" diff --git a/packages/docs/public/repl/index.html b/packages/docs/public/repl/index.html deleted file mode 100644 index 5dea08a9677..00000000000 --- a/packages/docs/public/repl/index.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - diff --git a/packages/docs/public/repl/repl-server.html b/packages/docs/public/repl/repl-server.html new file mode 100644 index 00000000000..907cb414789 --- /dev/null +++ b/packages/docs/public/repl/repl-server.html @@ -0,0 +1,120 @@ + + + + + Qwik REPL Server + + + + + + diff --git a/packages/docs/src/components/header/header.tsx b/packages/docs/src/components/header/header.tsx index 5267a62d736..6af26a19dc6 100644 --- a/packages/docs/src/components/header/header.tsx +++ b/packages/docs/src/components/header/header.tsx @@ -45,7 +45,7 @@ export const Header = component$(
  • - + Examples
  • diff --git a/packages/docs/src/components/page/page.tsx b/packages/docs/src/components/page/page.tsx index 2a40845e08f..0a8582f0b19 100644 --- a/packages/docs/src/components/page/page.tsx +++ b/packages/docs/src/components/page/page.tsx @@ -11,8 +11,11 @@ export const Page = component$(() => { if (loc.pathname === '/playground') { return ; } - if (loc.pathname === '/examples') { - return ; + + if (loc.pathname.startsWith('/examples/')) { + const p = loc.pathname.split('/'); + const appId = `${p[2]}/${p[3]}`; + return ; } const page = usePage(); diff --git a/packages/docs/src/components/repl/editor.tsx b/packages/docs/src/components/repl/editor.tsx index c700ca0389d..3d3f19eff5c 100644 --- a/packages/docs/src/components/repl/editor.tsx +++ b/packages/docs/src/components/repl/editor.tsx @@ -11,6 +11,7 @@ import { import type { TransformModuleInput } from '@builder.io/qwik/optimizer'; import { addQwikLib, ICodeEditorViewState, initMonacoEditor, updateMonacoEditor } from './monaco'; import type { IStandaloneCodeEditor } from './monaco'; +import type { ReplStore } from './types'; export const Editor = component$((props: EditorProps) => { const hostElm = useHostElement() as HTMLElement; @@ -24,7 +25,7 @@ export const Editor = component$((props: EditorProps) => { useClientEffect$(async () => { if (!store.editor) { - await initMonacoEditor(hostElm, props, store); + await initMonacoEditor(hostElm, props, store, props.store); } return () => { if (store.editor) { @@ -61,10 +62,10 @@ export interface EditorProps { inputs: TransformModuleInput[]; lineNumbers: 'on' | 'off'; onChangeQrl?: QRL<(path: string, code: string) => void>; - readOnly: boolean; selectedPath: string; wordWrap: 'on' | 'off'; version: string; + store: ReplStore; } export interface EditorStore { diff --git a/packages/docs/src/components/repl/monaco.tsx b/packages/docs/src/components/repl/monaco.tsx index 3f2e1ba39e7..41991e6ef38 100644 --- a/packages/docs/src/components/repl/monaco.tsx +++ b/packages/docs/src/components/repl/monaco.tsx @@ -1,12 +1,14 @@ import { noSerialize } from '@builder.io/qwik'; -import e from 'express'; +import type { Diagnostic } from '@builder.io/qwik/optimizer'; import type MonacoTypes from 'monaco-editor'; import type { EditorProps, EditorStore } from './editor'; +import type { ReplStore } from './types'; export const initMonacoEditor = async ( containerElm: any, props: EditorProps, - store: EditorStore + editorStore: EditorStore, + replStore: ReplStore ) => { const monaco = await getMonaco(); const ts = monaco.languages.typescript; @@ -39,22 +41,28 @@ export const initMonacoEditor = async ( ...defaultEditorOpts, ariaLabel: props.ariaLabel, lineNumbers: props.lineNumbers, - readOnly: props.readOnly, wordWrap: props.wordWrap, model: null, }); ts.typescriptDefaults.setEagerModelSync(true); - if (!props.readOnly && typeof props.onChangeQrl === 'object') { - store.onChangeSubscription = noSerialize( - editor.onDidChangeModelContent(() => { + if (typeof props.onChangeQrl === 'object') { + let tmrId: any = null; + + editorStore.onChangeSubscription = noSerialize( + editor.onDidChangeModelContent(async () => { props.onChangeQrl?.invoke(props.selectedPath, editor.getValue()); + + clearTimeout(tmrId); + tmrId = setTimeout(() => { + checkDiagnostics(monaco, editor, replStore); + }, 80); }) ); } - store.editor = noSerialize(editor); + editorStore.editor = noSerialize(editor); }; export const addQwikLib = async (version: string) => { @@ -67,7 +75,7 @@ export const addQwikLib = async (version: string) => { }); }; -export const updateMonacoEditor = async (props: EditorProps, store: EditorStore) => { +export const updateMonacoEditor = async (props: EditorProps, editorStore: EditorStore) => { const monaco = await getMonaco(); const fsPaths = props.inputs.map((i) => getUri(monaco, i.path).fsPath); @@ -94,31 +102,89 @@ export const updateMonacoEditor = async (props: EditorProps, store: EditorStore) } } - const selectedFsPath = getUri(monaco, props.selectedPath).fsPath; - const previousSelectedModel = store.editor!.getModel(); - if (!props.readOnly && previousSelectedModel) { - const viewState = store.editor!.saveViewState(); - if (viewState) { - store.viewStates[previousSelectedModel.uri.fsPath] = noSerialize(viewState); + if (editorStore.editor) { + const selectedFsPath = getUri(monaco, props.selectedPath).fsPath; + const previousSelectedModel = editorStore.editor.getModel(); + if (previousSelectedModel) { + const viewState = editorStore.editor.saveViewState(); + if (viewState) { + editorStore.viewStates[previousSelectedModel.uri.fsPath] = noSerialize(viewState); + } } - } - if (!previousSelectedModel || previousSelectedModel.uri.fsPath !== selectedFsPath) { - const selectedModel = monaco.editor.getModels().find((m) => m.uri.fsPath === selectedFsPath); - if (selectedModel) { - store.editor!.setModel(selectedModel); + if (!previousSelectedModel || previousSelectedModel.uri.fsPath !== selectedFsPath) { + const selectedModel = monaco.editor.getModels().find((m) => m.uri.fsPath === selectedFsPath); + if (selectedModel) { + editorStore.editor.setModel(selectedModel); - if (!props.readOnly) { - const viewState = store.viewStates[selectedModel.uri.fsPath]; + const viewState = editorStore.viewStates[selectedModel.uri.fsPath]; if (viewState) { - store.editor!.restoreViewState(viewState); + editorStore.editor.restoreViewState(viewState); } - store.editor!.focus(); + editorStore.editor.focus(); } } } }; +const checkDiagnostics = async ( + monaco: Monaco, + editor: IStandaloneCodeEditor, + replStore: ReplStore +) => { + if (!monacoCtx.tsWorker) { + const getTsWorker = await monaco.languages.typescript.getTypeScriptWorker(); + monacoCtx.tsWorker = await getTsWorker(editor.getModel()!.uri); + } + const tsWorker = monacoCtx.tsWorker; + + const models = monaco.editor.getModels(); + const tsDiagnostics: TypeScriptDiagnostic[] = []; + + await Promise.all( + models.map(async (m) => { + const filePath = `file://${m.uri.fsPath}`; + const semPromise = tsWorker.getSemanticDiagnostics(filePath); + const synPromise = tsWorker.getSyntacticDiagnostics(filePath); + tsDiagnostics.push(...(await semPromise)); + tsDiagnostics.push(...(await synPromise)); + }) + ); + + if (tsDiagnostics.length > 0) { + replStore.monacoDiagnostics = tsDiagnostics.map((tsd) => { + const d: Diagnostic = { + message: getTsDiagnosticMessage(tsd.messageText), + severity: 'Error', + code_highlights: [], + origin: 'monaco', + show_environment: false, + }; + return d; + }); + + if (replStore.selectedOutputPanel !== 'diagnostics') { + replStore.lastOutputPanel = replStore.selectedOutputPanel; + replStore.selectedOutputPanel = 'diagnostics'; + } + } else if (replStore.monacoDiagnostics.length > 0) { + replStore.monacoDiagnostics = []; + if (replStore.diagnostics.length === 0 && replStore.selectedOutputPanel === 'diagnostics') { + replStore.selectedOutputPanel = replStore.lastOutputPanel || 'app'; + } + } +}; + +const getTsDiagnosticMessage = (m: string | DiagnosticMessageChain) => { + if (m) { + if (typeof m === 'string') { + return m; + } + return m.messageText; + } + return ''; +}; + const getMonaco = async (): Promise => { if (!monacoCtx.loader) { // lazy-load the monaco AMD script ol' school @@ -127,6 +193,8 @@ const getMonaco = async (): Promise => { script.addEventListener('error', reject); script.addEventListener('load', () => { require.config({ paths: { vs: MONACO_VS_URL } }); + + // https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.js require(['vs/editor/editor.main'], () => { resolve((globalThis as any).monaco); }); @@ -211,6 +279,7 @@ const defaultEditorOpts: IStandaloneEditorConstructionOptions = { const monacoCtx: MonacoContext = { deps: [], loader: null, + tsWorker: null, }; const getCdnUrl = (pkgName: string, pkgVersion: string, pkgPath: string) => { @@ -227,10 +296,14 @@ export type ICodeEditorViewState = MonacoTypes.editor.ICodeEditorViewState; export type IStandaloneEditorConstructionOptions = MonacoTypes.editor.IStandaloneEditorConstructionOptions; export type IModelContentChangedEvent = MonacoTypes.editor.IModelContentChangedEvent; +export type TypeScriptWorker = MonacoTypes.languages.typescript.TypeScriptWorker; +export type TypeScriptDiagnostic = MonacoTypes.languages.typescript.Diagnostic; +export type DiagnosticMessageChain = MonacoTypes.languages.typescript.DiagnosticMessageChain; interface MonacoContext { deps: NodeModuleDep[]; loader: Promise | null; + tsWorker: null | TypeScriptWorker; } interface NodeModuleDep { diff --git a/packages/docs/src/components/repl/repl-input-panel.tsx b/packages/docs/src/components/repl/repl-input-panel.tsx index d0874aeb9d1..3c0573450f7 100644 --- a/packages/docs/src/components/repl/repl-input-panel.tsx +++ b/packages/docs/src/components/repl/repl-input-panel.tsx @@ -2,17 +2,18 @@ import type { QRL } from '@builder.io/qwik'; import { Editor } from './editor'; import { ReplTabButton } from './repl-tab-button'; import { ReplTabButtons } from './repl-tab-buttons'; -import type { ReplStore } from './types'; +import type { ReplStore, ReplModuleInput } from './types'; export const ReplInputPanel = ({ store, + inputs, onInputChangeQrl, onInputDeleteQrl, }: ReplInputPanelProps) => { return (
    - {store.inputs.map((input) => + {inputs.map((input) => input.hidden ? null : (
    @@ -54,6 +55,7 @@ const formatFilePath = (path: string) => { interface ReplInputPanelProps { store: ReplStore; + inputs: ReplModuleInput[]; onInputChangeQrl: QRL<(path: string, code: string) => void>; onInputDeleteQrl: QRL<(path: string) => void>; } diff --git a/packages/docs/src/components/repl/repl-options.tsx b/packages/docs/src/components/repl/repl-options.tsx index e4a8bab1d74..94f2cae8d8d 100644 --- a/packages/docs/src/components/repl/repl-options.tsx +++ b/packages/docs/src/components/repl/repl-options.tsx @@ -1,8 +1,6 @@ import type { ReplStore } from './types'; export const ReplOptions = ({ store }: ReplOptionsProps) => { - const versions = filterVersions(store.versions, store.version); - return (
    {
    ); }; -const filterVersions = (versions: string[], version: string | undefined) => { - if (!versions) { - if (version) { - return [version]; - } else { - return []; - } - } - - return versions.filter((v) => { - if (v === version) { - return true; - } - if (v.includes('-')) { - return false; - } - const parts = v.split('.'); - if (parts.length !== 3) { - return false; - } - if (isNaN(parts[2] as any)) { - return false; - } - if (parts[0] === '0' && parts[1] === '0') { - if (parseInt(parts[2], 10) < 20) { - return false; - } - } - return true; - }); -}; - const StoreOption = (props: StoreOptionProps) => { return (