Skip to content

Commit 8549e30

Browse files
kermanxantfu
andauthored
feat: add runnable monaco editor (#219) (#1273)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
1 parent cd60f35 commit 8549e30

File tree

27 files changed

+685
-29
lines changed

27 files changed

+685
-29
lines changed

demo/starter/slides.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,10 @@ hide: false
562562

563563
# Monaco Editor
564564

565+
Slidev provides built-in Moanco Editor support.
566+
567+
Add `{monaco}` to the code block to turn it into an editor:
568+
565569
```ts {monaco}
566570
import { ref } from 'vue'
567571
import hello from './external'
@@ -570,6 +574,18 @@ const code = ref('const a = 1')
570574
hello()
571575
```
572576

577+
Use `{monaco-run}` to create an editor that can execute the code directly in the slide:
578+
579+
```ts {monaco-run}
580+
function fibonacci(n: number): number {
581+
return n <= 1
582+
? n
583+
: fibonacci(n - 1) + fibonacci(n - 2) // you know, this is NOT the best way to do it :P
584+
}
585+
586+
console.log(Array.from({ length: 10 }, (_, i) => fibonacci(i + 1)))
587+
```
588+
573589
---
574590
layout: center
575591
class: text-center

demo/starter/vite.config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { defineConfig } from 'vite'
22

33
export default defineConfig({
4-
plugins: [
5-
],
4+
plugins: [],
65
})

demo/vue-runner/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"private": true,
3+
"scripts": {
4+
"build": "slidev build",
5+
"dev": "nodemon -w '../../packages/slidev/dist/*.mjs' --exec \"slidev ./slides.md --open=false --log=info --inspect\"",
6+
"export": "slidev export",
7+
"export-notes": "slidev export-notes"
8+
},
9+
"devDependencies": {
10+
"@slidev/cli": "workspace:*",
11+
"@slidev/theme-default": "^0.25.0",
12+
"@slidev/theme-seriph": "^0.25.0",
13+
"@vue/compiler-sfc": "^3.4.21",
14+
"nodemon": "^3.1.0",
15+
"vue": "^3.4.21"
16+
}
17+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* eslint-disable no-new-func */
2+
import { defineCodeRunnersSetup } from '@slidev/types'
3+
4+
export default defineCodeRunnersSetup(() => {
5+
return {
6+
// Support Vue SFC
7+
async vue(code) {
8+
const Vue = await import('vue')
9+
const { parse, compileScript } = await import('@vue/compiler-sfc')
10+
11+
// Compile the script, note this demo does not handle Vue styles
12+
const sfc = parse(code)
13+
let scripts = compileScript(sfc.descriptor, {
14+
id: sfc.descriptor.filename,
15+
genDefaultAs: '__Component',
16+
inlineTemplate: true,
17+
}).content
18+
19+
// Replace Vue imports to object destructuring
20+
// Only for simple demo, it doesn't work with imports from other packages
21+
scripts = scripts.replace(/import ({[^}]+}) from ['"]vue['"]/g, (_, imports) => `const ${imports.replace(/\sas\s/g, ':')} = Vue`)
22+
scripts += '\nreturn __Component'
23+
24+
// Create function to evaluate the script and get the component
25+
// Note this is not sandboxed, it's NOT secure.
26+
const component = new Function(`return (Vue) => {${scripts}}`)()(Vue)
27+
28+
// Mount the component
29+
const app = Vue.createApp(component)
30+
const el = document.createElement('div')
31+
app.mount(el)
32+
33+
return {
34+
element: el,
35+
}
36+
},
37+
}
38+
})

demo/vue-runner/setup/shiki.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineShikiSetup } from '@slidev/types'
2+
3+
export default defineShikiSetup(() => {
4+
return {
5+
themes: {
6+
dark: 'vitesse-dark',
7+
light: 'vitesse-light',
8+
},
9+
// Explicitly Vue for Monaco Highlighting
10+
langs: [
11+
'ts',
12+
'js',
13+
'vue',
14+
'html',
15+
],
16+
}
17+
})

demo/vue-runner/slides.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
layout: default
3+
---
4+
5+
# Simple Vue SFC Runner
6+
7+
<!-- eslint-skip -->
8+
9+
```vue {monaco-run}
10+
<script setup>
11+
import { computed, ref } from 'vue'
12+
const counter = ref(1)
13+
const doubled = computed(() => counter.value * 2)
14+
function inc() { counter.value++ }
15+
</script>
16+
17+
<template>
18+
<div class="select-none text-lg flex gap-4 items-center">
19+
<span class="text-gray text-lg">
20+
<span class="text-orange">{{ counter }}</span>
21+
* 2 =
22+
<span class="text-green">{{ doubled }}</span>
23+
</span>
24+
<button class="border border-main p2 rounded" @click="inc">+1</button>
25+
<button class="border border-main p2 rounded" @click="counter -= 1">-1</button>
26+
</div>
27+
</template>
28+
```
29+
30+
---
31+
32+
This is a demo to prove the extensibility of Slidev Code Runners.
33+
34+
Refer to `./setup/monaco-runner.ts` for the implementation.
35+
36+
Note that there is a lot of edge cases that this demo is not handling. Extra work is needed to make it production ready.

docs/.vitepress/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ const Customizations: (DefaultTheme.NavItemWithLink | DefaultTheme.NavItemChildr
145145
text: 'Configure Shortcuts',
146146
link: '/custom/config-shortcuts',
147147
},
148+
{
149+
text: 'Configure Code Runners',
150+
link: '/custom/config-code-runners',
151+
},
148152
{
149153
text: 'Vue Global Context',
150154
link: '/custom/vue-context',

docs/custom/config-code-runners.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Configure Code Runners
2+
3+
<Environment type="client" />
4+
5+
Define code runners for custom languages in your Monaco Editor.
6+
7+
By default, JavaScript, TypeScript runners are supported built-in. They runs in the browser with **without** sandbox environment. If you want to more advanced integrations, you might want to provide your own code runners that sends the code to a remote server, runs in a Web Worker, or anything, up to you.
8+
9+
Create `./setup/code-runners.ts` with the following content:
10+
11+
```ts
12+
import { defineCodeRunnersSetup } from '@slidev/types'
13+
14+
export default defineCodeRunnersSetup(() => {
15+
return {
16+
async python(code, ctx) {
17+
// Somehow execute the code and return the result
18+
const result = await executePythonCodeRemotely(code)
19+
return {
20+
text: result
21+
}
22+
},
23+
html(code, ctx) {
24+
return {
25+
html: sanitizeHtml(code)
26+
}
27+
},
28+
// or other languages, key is the language id
29+
}
30+
})
31+
```

docs/custom/config-monaco.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,7 @@ Since v0.48.0, the Monaco editor is enabled by default and only be bundled when
108108
monaco: false # can also be `dev` or `build` tp conditionally enable it
109109
---
110110
```
111+
112+
## Configure Code Runners
113+
114+
To configure how the Monaco Runner runs the code, or to add support for custom languages, please reference to [Configure Code Runners](/custom/config-code-runners).

docs/custom/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ highlighter: shiki
4242
# show line numbers in code blocks
4343
lineNumbers: false
4444
# enable monaco editor, can be boolean, 'dev' or 'build'
45-
monaco: dev
45+
monaco: true
4646
# Where to load monaco types from, can be 'cdn', 'local' or 'none'
4747
monacoTypesSource: local
4848
# explicitly specify extra local packages to import the types for

0 commit comments

Comments
 (0)