Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 52a193a

Browse filesBrowse files
feat: show SSR output (#343)
1 parent 5e092b6 commit 52a193a
Copy full SHA for 52a193a

File tree

Expand file treeCollapse file tree

8 files changed

+106
-21
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+106
-21
lines changed

‎src/Repl.vue

Copy file name to clipboardExpand all lines: src/Repl.vue
+3Lines 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

Copy file name to clipboardExpand all lines: src/output/Output.vue
+27-16Lines 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

Copy file name to clipboardExpand all lines: src/output/Sandbox.vue
+29-2Lines 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

Copy file name to clipboard
+32Lines 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

Copy file name to clipboardExpand all lines: src/output/srcdoc.html
+7-2Lines 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

Copy file name to clipboardExpand all lines: src/store.ts
+6Lines 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

Copy file name to clipboardExpand all lines: src/types.ts
+1-1Lines 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

Copy file name to clipboardExpand all lines: test/main.ts
+1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.