Skip to content

Commit 52a193a

Browse files
feat: show SSR output (#343)
1 parent 5e092b6 commit 52a193a

File tree

8 files changed

+106
-21
lines changed

8 files changed

+106
-21
lines changed

src/Repl.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface Props {
1919
autoResize?: boolean
2020
showCompileOutput?: boolean
2121
showImportMap?: boolean
22+
showSsrOutput?: boolean
2223
showTsConfig?: boolean
2324
clearConsole?: boolean
2425
layout?: 'horizontal' | 'vertical'
@@ -54,6 +55,7 @@ const props = withDefaults(defineProps<Props>(), {
5455
autoResize: true,
5556
showCompileOutput: true,
5657
showImportMap: true,
58+
showSsrOutput: false,
5759
showTsConfig: true,
5860
clearConsole: true,
5961
layoutReverse: false,
@@ -105,6 +107,7 @@ defineExpose({ reload })
105107
ref="output"
106108
:editor-component="editor"
107109
:show-compile-output="props.showCompileOutput"
110+
:show-ssr-output="props.showSsrOutput"
108111
:ssr="!!props.ssr"
109112
/>
110113
</template>

src/output/Output.vue

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import Preview from './Preview.vue'
3-
import { computed, inject, useTemplateRef } from 'vue'
3+
import SsrOutput from './SsrOutput.vue'
4+
import { computed, inject, useTemplateRef, watchEffect } from 'vue'
45
import {
56
type EditorComponentType,
67
type OutputModes,
@@ -10,27 +11,32 @@ import {
1011
const props = defineProps<{
1112
editorComponent: EditorComponentType
1213
showCompileOutput?: boolean
14+
showSsrOutput?: boolean
1315
ssr: boolean
1416
}>()
1517
1618
const { store } = inject(injectKeyProps)!
1719
const previewRef = useTemplateRef('preview')
18-
const modes = computed(() =>
19-
props.showCompileOutput
20-
? (['preview', 'js', 'css', 'ssr'] as const)
21-
: (['preview'] as const),
22-
)
20+
const modes = computed(() => {
21+
const outputModes: OutputModes[] = ['preview']
22+
if (props.showCompileOutput) {
23+
outputModes.push('js', 'css', 'ssr')
24+
}
25+
if (props.ssr && props.showSsrOutput) {
26+
outputModes.push('ssr output')
27+
}
28+
return outputModes
29+
})
2330
2431
const mode = computed<OutputModes>({
25-
get: () =>
26-
(modes.value as readonly string[]).includes(store.value.outputMode)
27-
? store.value.outputMode
28-
: 'preview',
29-
set(value) {
30-
if ((modes.value as readonly string[]).includes(store.value.outputMode)) {
31-
store.value.outputMode = value
32-
}
33-
},
32+
get: () => store.value.outputMode,
33+
set: (value) => (store.value.outputMode = value),
34+
})
35+
36+
watchEffect(() => {
37+
if (!modes.value.includes(mode.value)) {
38+
mode.value = modes.value[0]
39+
}
3440
})
3541
3642
function reload() {
@@ -54,8 +60,13 @@ defineExpose({ reload, previewRef })
5460

5561
<div class="output-container">
5662
<Preview ref="preview" :show="mode === 'preview'" :ssr="ssr" />
63+
<SsrOutput
64+
v-if="mode === 'ssr output'"
65+
:context="store.ssrOutput.context"
66+
:html="store.ssrOutput.html"
67+
/>
5768
<props.editorComponent
58-
v-if="mode !== 'preview'"
69+
v-else-if="mode !== 'preview'"
5970
readonly
6071
:filename="store.activeFile.filename"
6172
:value="store.activeFile.compiled[mode]"

src/output/Sandbox.vue

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ async function updatePreview() {
223223
console.info(
224224
`[@vue/repl] successfully compiled ${ssrModules.length} modules for SSR.`,
225225
)
226-
await proxy.eval([
226+
store.value.ssrOutput.html = store.value.ssrOutput.context = ''
227+
const response = await proxy.eval([
227228
`const __modules__ = {};`,
228229
...ssrModules,
229230
`import { renderToString as _renderToString } from 'vue/server-renderer'
@@ -235,15 +236,41 @@ async function updatePreview() {
235236
app.config.unwrapInjectedRef = true
236237
}
237238
app.config.warnHandler = () => {}
238-
window.__ssr_promise__ = _renderToString(app).then(html => {
239+
const rawContext = {}
240+
window.__ssr_promise__ = _renderToString(app, rawContext).then(html => {
239241
document.body.innerHTML = '<div id="app">' + html + '</div>' + \`${
240242
previewOptions.value?.bodyHTML || ''
241243
}\`
244+
const safeContext = {}
245+
const isSafe = (v) =>
246+
v === null ||
247+
typeof v === 'boolean' ||
248+
typeof v === 'string' ||
249+
Number.isFinite(v)
250+
const toSafe = (v) => (isSafe(v) ? v : '[' + typeof v + ']')
251+
for (const prop in rawContext) {
252+
const value = rawContext[prop]
253+
safeContext[prop] = isSafe(value)
254+
? value
255+
: Array.isArray(value)
256+
? value.map(toSafe)
257+
: typeof value === 'object'
258+
? Object.fromEntries(
259+
Object.entries(value).map(([k, v]) => [k, toSafe(v)]),
260+
)
261+
: toSafe(value)
262+
}
263+
return { ssrHtml: html, ssrContext: safeContext }
242264
}).catch(err => {
243265
console.error("SSR Error", err)
244266
})
245267
`,
246268
])
269+
270+
if (response) {
271+
store.value.ssrOutput.html = String((response as any).ssrHtml ?? '')
272+
store.value.ssrOutput.context = (response as any).ssrContext || ''
273+
}
247274
}
248275
249276
// compile code to simulated module system

src/output/SsrOutput.vue

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
html: string
4+
context: unknown
5+
}>()
6+
</script>
7+
8+
<template>
9+
<div class="ssr-output">
10+
<strong>HTML</strong>
11+
<pre class="ssr-output-pre">{{ html }}</pre>
12+
<strong>Context</strong>
13+
<pre class="ssr-output-pre">{{ context }}</pre>
14+
</div>
15+
</template>
16+
17+
<style scoped>
18+
.ssr-output {
19+
background: var(--bg);
20+
box-sizing: border-box;
21+
color: var(--text-light);
22+
height: 100%;
23+
overflow: auto;
24+
padding: 10px;
25+
width: 100%;
26+
}
27+
28+
.ssr-output-pre {
29+
font-family: var(--font-code);
30+
white-space: pre-wrap;
31+
}
32+
</style>

src/output/srcdoc.html

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
const send_message = (payload) =>
3636
parent.postMessage({ ...payload }, ev.origin)
3737
const send_reply = (payload) => send_message({ ...payload, cmd_id })
38-
const send_ok = () => send_reply({ action: 'cmd_ok' })
38+
const send_ok = (response) =>
39+
send_reply({ action: 'cmd_ok', args: response })
3940
const send_error = (message, stack) =>
4041
send_reply({ action: 'cmd_error', message, stack })
4142

@@ -65,7 +66,11 @@
6566
scriptEls.push(scriptEl)
6667
await done
6768
}
68-
send_ok()
69+
if (window.__ssr_promise__) {
70+
send_ok(await window.__ssr_promise__)
71+
} else {
72+
send_ok()
73+
}
6974
} catch (e) {
7075
send_error(e.message, e.stack)
7176
}

src/store.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ export function useStore(
368368
showOutput,
369369
outputMode,
370370
sfcOptions,
371+
ssrOutput: { html: '', context: '' },
371372
compiler,
372373
loading,
373374
vueVersion,
@@ -429,6 +430,10 @@ export type StoreState = ToRefs<{
429430
showOutput: boolean
430431
outputMode: OutputModes
431432
sfcOptions: SFCOptions
433+
ssrOutput: {
434+
html: string
435+
context: unknown
436+
}
432437
/** `@vue/compiler-sfc` */
433438
compiler: typeof defaultCompiler
434439
/* only apply for compiler-sfc */
@@ -474,6 +479,7 @@ export type Store = Pick<
474479
| 'showOutput'
475480
| 'outputMode'
476481
| 'sfcOptions'
482+
| 'ssrOutput'
477483
| 'compiler'
478484
| 'vueVersion'
479485
| 'locale'

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface EditorEmits {
1313
}
1414
export type EditorComponentType = Component<EditorProps>
1515

16-
export type OutputModes = 'preview' | EditorMode
16+
export type OutputModes = 'preview' | 'ssr output' | EditorMode
1717

1818
export const injectKeyProps: InjectionKey<
1919
ToRefs<Required<Props & { autoSave: boolean }>>

test/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const App = {
6060
editor: MonacoEditor,
6161
// layout: 'vertical',
6262
ssr: true,
63+
showSsrOutput: true,
6364
sfcOptions: {
6465
script: {
6566
// inlineTemplate: false

0 commit comments

Comments
 (0)