Skip to content

Commit 03890a3

Browse files
committed
feat: monaco setup
1 parent ba49c20 commit 03890a3

11 files changed

Lines changed: 233 additions & 114 deletions

File tree

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Presentation <b>slide</b>s for <b>dev</b>elopers 🧑‍💻👩‍💻👨‍
1111

1212
## Motivation
1313

14-
I am making this because I found myself spend too much on layouting and styling slides when using apps like PowerPoint / Keynote / Google Slides. When sharing code snippets, I would also need to use other tools to generate the highlighted code as images over and over again. Which are just not ideal to me.
14+
I am making this because I found myself spending too much on layouting and styling slides when using apps like PowerPoint / Keynote / Google Slides. When sharing code snippets, I would also need to use other tools to generate the highlighted code as images over and over again. Which are just not ideal to me.
1515

1616
So as a frontend developer, why not solve it the way that fits better with what I am good that?
1717

@@ -23,7 +23,7 @@ Status: **Alpha**
2323

2424
~~Currently, I will focus more on the content of the slides I need myself. I will update the slides of my next talk with it (you can also have a preview of it :P). **Think it as a template for making slides at this moment**. After finishing my talk, I will make this a standalone tool like VitePress where all you need is a command with a markdown file.~~
2525

26-
Alright, I broke my words again, it's now available as an standalone tool 🎉
26+
Alright, I broke my words again. It's now available as a standalone tool 🎉
2727

2828
**Docs and guides on [slidev.antfu.me](https://slidev.antfu.me)**
2929

@@ -34,15 +34,18 @@ For a full example, you can check the [demo](./demo) folder, which is a draft fo
3434
## TODO
3535

3636
- [ ] Foot notes
37-
- [ ] Custom Monaco setup
3837
- [ ] Configuration in header
3938
- [ ] Preload next slide
4039
- [ ] Shiki + TwoSlash?
41-
- [ ] Embedded editor
42-
- [ ] Hide dev buttons on build
4340
- [ ] VS Code extension
41+
- [ ] Presentor View
4442
- [ ] Timer
4543
- [ ] A few more themes
44+
- [ ] Dialog before recording
45+
- [ ] Docs
46+
- [x] Hide dev buttons on build
47+
- [x] Embedded editor
48+
- [x] Custom Monaco setup
4649
- [x] Camera view
4750
- [x] Recording!
4851
- [x] Markdown to Slides

packages/client/App.vue

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
<script setup lang="ts">
2-
import { useHead } from '@vueuse/head'
32
import { computed, provide } from 'vue'
43
import { useNavigateControls } from './logic'
54
import { scale, targetHeight, targetWidth, offsetRight } from './logic/scale'
65
import { injectClickDisabled } from './modules/directives'
76
import Controls from './internals/Controls.vue'
7+
import setupHead from './setup/head'
88
9-
useHead({
10-
title: 'Slidev',
11-
meta: [],
12-
})
9+
setupHead()
1310
1411
const controls = useNavigateControls()
1512
const style = computed(() => ({
@@ -31,9 +28,11 @@ function onClick(e: MouseEvent) {
3128

3229
<template>
3330
<div class="page-root">
34-
<div class="slide-root"
35-
@click="onClick">
36-
<div class="slide-container" id="slide-container" :style="style">
31+
<div
32+
class="slide-root"
33+
@click="onClick"
34+
>
35+
<div id="slide-container" class="slide-container" :style="style">
3736
<RouterView :class="controls.currentRoute.value?.meta?.class || ''" />
3837
</div>
3938
</div>

packages/client/builtin/Monaco.vue

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
</template>
44

55
<script setup lang="ts">
6-
import { ref, onMounted, onUnmounted, defineProps, watch, computed } from 'vue'
6+
import { ref, onUnmounted, defineProps, watch, computed } from 'vue'
77
import { ignorableWatch } from '@vueuse/core'
88
import { decode } from 'js-base64'
9+
import type * as monaco from 'monaco-editor'
910
import { formatCode } from '../setup/prettier'
10-
import { monaco } from '../setup/monaco'
11+
import setupMonaco from '../setup/monaco'
1112
import { isDark, useNavigateControls } from '../logic'
1213
1314
const props = defineProps({
@@ -60,70 +61,71 @@ const ext = computed(() => {
6061
}
6162
})
6263
63-
onMounted(() => {
64-
const model = monaco.editor.createModel(
65-
code.value,
66-
lang.value,
67-
monaco.Uri.parse(`file:///root/${Date.now()}.${ext.value}`),
68-
)
69-
70-
editor = monaco.editor.create(el.value!, {
71-
model,
72-
tabSize: 2,
73-
insertSpaces: true,
74-
detectIndentation: false,
75-
folding: false,
76-
fontSize: 12,
77-
fontFamily: '\'Fira Code\', monospace',
78-
lineDecorationsWidth: 0,
79-
lineNumbersMinChars: 0,
80-
scrollBeyondLastLine: false,
81-
scrollBeyondLastColumn: 0,
82-
automaticLayout: true,
83-
readOnly: props.readonly,
84-
theme: isDark.value ? 'vitesse-dark' : 'vitesse-light',
85-
lineNumbers: props.lineNumbers as any,
86-
glyphMargin: false,
87-
scrollbar: {
88-
useShadows: false,
89-
vertical: 'hidden',
90-
horizontal: 'hidden',
91-
},
92-
overviewRulerLanes: 0,
93-
minimap: { enabled: false },
94-
})
95-
editor.onDidFocusEditorText(() => controls.paused.value = true)
96-
editor.onDidBlurEditorText(() => controls.paused.value = false)
97-
98-
async function format() {
99-
code.value = (await formatCode(code.value, lang.value)).trim()
100-
}
101-
102-
const { ignoreUpdates } = ignorableWatch(code, (v) => {
103-
const selection = editor.getSelection()
104-
editor.setValue(v)
105-
if (selection)
106-
editor.setSelection(selection)
107-
})
108-
109-
model.onDidChangeContent(() => {
110-
const v = editor.getValue().toString()
111-
if (v !== code.value)
112-
ignoreUpdates(() => code.value = v)
113-
})
114-
115-
// ctrl+s to format
116-
editor.onKeyDown((e) => {
117-
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') {
118-
e.preventDefault()
119-
format()
64+
setupMonaco()
65+
.then(({ monaco }) => {
66+
const model = monaco.editor.createModel(
67+
code.value,
68+
lang.value,
69+
monaco.Uri.parse(`file:///root/${Date.now()}.${ext.value}`),
70+
)
71+
72+
editor = monaco.editor.create(el.value!, {
73+
model,
74+
tabSize: 2,
75+
insertSpaces: true,
76+
detectIndentation: false,
77+
folding: false,
78+
fontSize: 12,
79+
fontFamily: '\'Fira Code\', monospace',
80+
lineDecorationsWidth: 0,
81+
lineNumbersMinChars: 0,
82+
scrollBeyondLastLine: false,
83+
scrollBeyondLastColumn: 0,
84+
automaticLayout: true,
85+
readOnly: props.readonly,
86+
theme: isDark.value ? 'vitesse-dark' : 'vitesse-light',
87+
lineNumbers: props.lineNumbers as any,
88+
glyphMargin: false,
89+
scrollbar: {
90+
useShadows: false,
91+
vertical: 'hidden',
92+
horizontal: 'hidden',
93+
},
94+
overviewRulerLanes: 0,
95+
minimap: { enabled: false },
96+
})
97+
editor.onDidFocusEditorText(() => controls.paused.value = true)
98+
editor.onDidBlurEditorText(() => controls.paused.value = false)
99+
100+
async function format() {
101+
code.value = (await formatCode(code.value, lang.value)).trim()
120102
}
121-
})
122-
})
123103
124-
watch(isDark, () => monaco.editor.setTheme(isDark.value ? 'vitesse-dark' : 'vitesse-light'))
104+
const { ignoreUpdates } = ignorableWatch(code, (v) => {
105+
const selection = editor.getSelection()
106+
editor.setValue(v)
107+
if (selection)
108+
editor.setSelection(selection)
109+
})
110+
111+
model.onDidChangeContent(() => {
112+
const v = editor.getValue().toString()
113+
if (v !== code.value)
114+
ignoreUpdates(() => code.value = v)
115+
})
116+
117+
// ctrl+s to format
118+
editor.onKeyDown((e) => {
119+
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') {
120+
e.preventDefault()
121+
format()
122+
}
123+
})
124+
125+
watch(isDark, () => monaco.editor.setTheme(isDark.value ? 'vitesse-dark' : 'vitesse-light'))
126+
})
125127
126-
onUnmounted(() => editor.dispose())
128+
onUnmounted(() => editor?.dispose())
127129
</script>
128130
<style lang="postcss">
129131
.vue-monaco {

packages/client/setup/head.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useHead } from '@vueuse/head'
2+
3+
/* __imports__ */
4+
5+
export default function setupHead() {
6+
useHead({
7+
title: 'Slidev',
8+
link: [
9+
{ rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' },
10+
],
11+
})
12+
13+
/* __injections__ */
14+
}

packages/client/setup/monaco.ts

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,60 @@
1+
import { getCurrentInstance, onMounted } from 'vue'
12
import * as monaco from 'monaco-editor'
2-
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
3-
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
4-
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
5-
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
6-
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
73

8-
import '/@monaco-types/vue'
9-
import '/@monaco-types/@vueuse/core'
4+
/* __imports__ */
105

11-
import dark from 'theme-vitesse/themes/vitesse-dark.json'
12-
import light from 'theme-vitesse/themes/vitesse-light.json'
6+
export default async function setupMonaco() {
7+
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
8+
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
9+
noUnusedLocals: false,
10+
noUnusedParameters: false,
11+
allowUnreachableCode: true,
12+
allowUnusedLabels: true,
13+
strict: true,
14+
})
1315

14-
light.colors['editor.background'] = '#00000000'
15-
dark.colors['editor.background'] = '#00000000'
16+
await Promise.all([
17+
// @ts-expect-error
18+
import('/@monaco-types/vue'),
19+
// @ts-expect-error
20+
import('/@monaco-types/@vueuse/core'),
21+
// load workers
22+
(async() => {
23+
const [
24+
{ default: EditorWorker },
25+
{ default: JsonWorker },
26+
{ default: CssWorker },
27+
{ default: HtmlWorker },
28+
{ default: TsWorker },
29+
] = await Promise.all([
30+
import('monaco-editor/esm/vs/editor/editor.worker?worker'),
31+
import('monaco-editor/esm/vs/language/json/json.worker?worker'),
32+
import('monaco-editor/esm/vs/language/css/css.worker?worker'),
33+
import('monaco-editor/esm/vs/language/html/html.worker?worker'),
34+
import('monaco-editor/esm/vs/language/typescript/ts.worker?worker'),
35+
])
1636

17-
monaco.editor.defineTheme('vitesse-light', light as any)
18-
monaco.editor.defineTheme('vitesse-dark', dark as any)
37+
// @ts-expect-error
38+
window.MonacoEnvironment = {
39+
getWorker(_: any, label: string) {
40+
if (label === 'json')
41+
return new JsonWorker()
42+
if (label === 'css' || label === 'scss' || label === 'less')
43+
return new CssWorker()
44+
if (label === 'html' || label === 'handlebars' || label === 'razor')
45+
return new HtmlWorker()
46+
if (label === 'typescript' || label === 'javascript')
47+
return new TsWorker()
48+
return new EditorWorker()
49+
},
50+
}
51+
})(),
52+
])
1953

20-
// @ts-expect-error
21-
self.MonacoEnvironment = {
22-
getWorker(_: any, label: string) {
23-
if (label === 'json')
24-
return new JsonWorker()
25-
if (label === 'css' || label === 'scss' || label === 'less')
26-
return new CssWorker()
27-
if (label === 'html' || label === 'handlebars' || label === 'razor')
28-
return new HtmlWorker()
29-
if (label === 'typescript' || label === 'javascript')
30-
return new TsWorker()
31-
return new EditorWorker()
32-
},
33-
}
54+
/* __async_injections__ */
3455

35-
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
36-
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
37-
noUnusedLocals: false,
38-
noUnusedParameters: false,
39-
allowUnreachableCode: true,
40-
allowUnusedLabels: true,
41-
strict: true,
42-
})
56+
if (getCurrentInstance())
57+
await new Promise<void>(resolve => onMounted(resolve))
4358

44-
export { monaco }
59+
return { monaco }
60+
}

packages/slidev/node/plugins/preset.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { createSlidesLoader } from './slides'
1313
import { createMonacoLoader, transformMarkdownMonaco } from './monaco'
1414
import { createEntryPlugin } from './entry'
1515
import { resolveOptions, SlidevPluginOptions } from './options'
16+
import { createSetupPlugin } from './setups'
1617

1718
export function ViteSlidevPlugin(options: SlidevPluginOptions = {}): Plugin[] {
1819
const {
@@ -109,6 +110,7 @@ export function ViteSlidevPlugin(options: SlidevPluginOptions = {}): Plugin[] {
109110
createConfigPlugin(),
110111
createEntryPlugin(slidesOptions),
111112
createSlidesLoader(slidesOptions),
113+
createSetupPlugin(slidesOptions),
112114
createMonacoLoader(),
113115
]
114116
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { existsSync } from 'fs'
2+
import { join, resolve } from 'path'
3+
import { slash } from '@antfu/utils'
4+
import { Plugin } from 'vite'
5+
import { ResolvedSlidevOptions } from './options'
6+
7+
export function createSetupPlugin({ clientRoot, themeRoot, userRoot }: ResolvedSlidevOptions): Plugin {
8+
const setupEntry = slash(resolve(clientRoot, 'setup'))
9+
10+
return {
11+
name: 'slidev:setup',
12+
enforce: 'pre',
13+
async transform(code, id) {
14+
if (id.startsWith(setupEntry)) {
15+
const name = id.slice(setupEntry.length + 1)
16+
const imports: string[] = []
17+
const injections: string[] = []
18+
const asyncInjections: string[] = []
19+
20+
const setups = [
21+
join(themeRoot, 'setup', name),
22+
join(userRoot, 'setup', name),
23+
]
24+
25+
setups.forEach((path, idx) => {
26+
if (!existsSync(path))
27+
return
28+
29+
imports.push(`import __n${idx} from '/@fs${slash(path)}'`)
30+
injections.push(
31+
`// ${path}`,
32+
`__n${idx}()`,
33+
)
34+
asyncInjections.push(
35+
`// ${path}`,
36+
`await __n${idx}()`,
37+
)
38+
})
39+
40+
code = code.replace('/* __imports__ */', imports.join('\n'))
41+
code = code.replace('/* __injections__ */', injections.join('\n'))
42+
code = code.replace('/* __async_injections__ */', asyncInjections.join('\n'))
43+
return code
44+
}
45+
46+
return null
47+
},
48+
}
49+
}

0 commit comments

Comments
 (0)