Skip to content

Commit

Permalink
feat: monaco setup
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 23, 2021
1 parent ba49c20 commit 03890a3
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 114 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Presentation <b>slide</b>s for <b>dev</b>elopers 🧑‍💻👩‍💻👨‍

## Motivation

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.
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.

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

Expand All @@ -23,7 +23,7 @@ Status: **Alpha**

~~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.~~

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

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

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

- [ ] Foot notes
- [ ] Custom Monaco setup
- [ ] Configuration in header
- [ ] Preload next slide
- [ ] Shiki + TwoSlash?
- [ ] Embedded editor
- [ ] Hide dev buttons on build
- [ ] VS Code extension
- [ ] Presentor View
- [ ] Timer
- [ ] A few more themes
- [ ] Dialog before recording
- [ ] Docs
- [x] Hide dev buttons on build
- [x] Embedded editor
- [x] Custom Monaco setup
- [x] Camera view
- [x] Recording!
- [x] Markdown to Slides
Expand Down
15 changes: 7 additions & 8 deletions packages/client/App.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
<script setup lang="ts">
import { useHead } from '@vueuse/head'
import { computed, provide } from 'vue'
import { useNavigateControls } from './logic'
import { scale, targetHeight, targetWidth, offsetRight } from './logic/scale'
import { injectClickDisabled } from './modules/directives'
import Controls from './internals/Controls.vue'
import setupHead from './setup/head'
useHead({
title: 'Slidev',
meta: [],
})
setupHead()
const controls = useNavigateControls()
const style = computed(() => ({
Expand All @@ -31,9 +28,11 @@ function onClick(e: MouseEvent) {

<template>
<div class="page-root">
<div class="slide-root"
@click="onClick">
<div class="slide-container" id="slide-container" :style="style">
<div
class="slide-root"
@click="onClick"
>
<div id="slide-container" class="slide-container" :style="style">
<RouterView :class="controls.currentRoute.value?.meta?.class || ''" />
</div>
</div>
Expand Down
128 changes: 65 additions & 63 deletions packages/client/builtin/Monaco.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, defineProps, watch, computed } from 'vue'
import { ref, onUnmounted, defineProps, watch, computed } from 'vue'
import { ignorableWatch } from '@vueuse/core'
import { decode } from 'js-base64'
import type * as monaco from 'monaco-editor'
import { formatCode } from '../setup/prettier'
import { monaco } from '../setup/monaco'
import setupMonaco from '../setup/monaco'
import { isDark, useNavigateControls } from '../logic'
const props = defineProps({
Expand Down Expand Up @@ -60,70 +61,71 @@ const ext = computed(() => {
}
})
onMounted(() => {
const model = monaco.editor.createModel(
code.value,
lang.value,
monaco.Uri.parse(`file:///root/${Date.now()}.${ext.value}`),
)
editor = monaco.editor.create(el.value!, {
model,
tabSize: 2,
insertSpaces: true,
detectIndentation: false,
folding: false,
fontSize: 12,
fontFamily: '\'Fira Code\', monospace',
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
scrollBeyondLastLine: false,
scrollBeyondLastColumn: 0,
automaticLayout: true,
readOnly: props.readonly,
theme: isDark.value ? 'vitesse-dark' : 'vitesse-light',
lineNumbers: props.lineNumbers as any,
glyphMargin: false,
scrollbar: {
useShadows: false,
vertical: 'hidden',
horizontal: 'hidden',
},
overviewRulerLanes: 0,
minimap: { enabled: false },
})
editor.onDidFocusEditorText(() => controls.paused.value = true)
editor.onDidBlurEditorText(() => controls.paused.value = false)
async function format() {
code.value = (await formatCode(code.value, lang.value)).trim()
}
const { ignoreUpdates } = ignorableWatch(code, (v) => {
const selection = editor.getSelection()
editor.setValue(v)
if (selection)
editor.setSelection(selection)
})
model.onDidChangeContent(() => {
const v = editor.getValue().toString()
if (v !== code.value)
ignoreUpdates(() => code.value = v)
})
// ctrl+s to format
editor.onKeyDown((e) => {
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') {
e.preventDefault()
format()
setupMonaco()
.then(({ monaco }) => {
const model = monaco.editor.createModel(
code.value,
lang.value,
monaco.Uri.parse(`file:///root/${Date.now()}.${ext.value}`),
)
editor = monaco.editor.create(el.value!, {
model,
tabSize: 2,
insertSpaces: true,
detectIndentation: false,
folding: false,
fontSize: 12,
fontFamily: '\'Fira Code\', monospace',
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
scrollBeyondLastLine: false,
scrollBeyondLastColumn: 0,
automaticLayout: true,
readOnly: props.readonly,
theme: isDark.value ? 'vitesse-dark' : 'vitesse-light',
lineNumbers: props.lineNumbers as any,
glyphMargin: false,
scrollbar: {
useShadows: false,
vertical: 'hidden',
horizontal: 'hidden',
},
overviewRulerLanes: 0,
minimap: { enabled: false },
})
editor.onDidFocusEditorText(() => controls.paused.value = true)
editor.onDidBlurEditorText(() => controls.paused.value = false)
async function format() {
code.value = (await formatCode(code.value, lang.value)).trim()
}
})
})
watch(isDark, () => monaco.editor.setTheme(isDark.value ? 'vitesse-dark' : 'vitesse-light'))
const { ignoreUpdates } = ignorableWatch(code, (v) => {
const selection = editor.getSelection()
editor.setValue(v)
if (selection)
editor.setSelection(selection)
})
model.onDidChangeContent(() => {
const v = editor.getValue().toString()
if (v !== code.value)
ignoreUpdates(() => code.value = v)
})
// ctrl+s to format
editor.onKeyDown((e) => {
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') {
e.preventDefault()
format()
}
})
watch(isDark, () => monaco.editor.setTheme(isDark.value ? 'vitesse-dark' : 'vitesse-light'))
})
onUnmounted(() => editor.dispose())
onUnmounted(() => editor?.dispose())
</script>
<style lang="postcss">
.vue-monaco {
Expand Down
14 changes: 14 additions & 0 deletions packages/client/setup/head.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useHead } from '@vueuse/head'

/* __imports__ */

export default function setupHead() {
useHead({
title: 'Slidev',
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' },
],
})

/* __injections__ */
}
88 changes: 52 additions & 36 deletions packages/client/setup/monaco.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,60 @@
import { getCurrentInstance, onMounted } from 'vue'
import * as monaco from 'monaco-editor'
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'

import '/@monaco-types/vue'
import '/@monaco-types/@vueuse/core'
/* __imports__ */

import dark from 'theme-vitesse/themes/vitesse-dark.json'
import light from 'theme-vitesse/themes/vitesse-light.json'
export default async function setupMonaco() {
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
noUnusedLocals: false,
noUnusedParameters: false,
allowUnreachableCode: true,
allowUnusedLabels: true,
strict: true,
})

light.colors['editor.background'] = '#00000000'
dark.colors['editor.background'] = '#00000000'
await Promise.all([
// @ts-expect-error
import('/@monaco-types/vue'),
// @ts-expect-error
import('/@monaco-types/@vueuse/core'),
// load workers
(async() => {
const [
{ default: EditorWorker },
{ default: JsonWorker },
{ default: CssWorker },
{ default: HtmlWorker },
{ default: TsWorker },
] = await Promise.all([
import('monaco-editor/esm/vs/editor/editor.worker?worker'),
import('monaco-editor/esm/vs/language/json/json.worker?worker'),
import('monaco-editor/esm/vs/language/css/css.worker?worker'),
import('monaco-editor/esm/vs/language/html/html.worker?worker'),
import('monaco-editor/esm/vs/language/typescript/ts.worker?worker'),
])

monaco.editor.defineTheme('vitesse-light', light as any)
monaco.editor.defineTheme('vitesse-dark', dark as any)
// @ts-expect-error
window.MonacoEnvironment = {
getWorker(_: any, label: string) {
if (label === 'json')
return new JsonWorker()
if (label === 'css' || label === 'scss' || label === 'less')
return new CssWorker()
if (label === 'html' || label === 'handlebars' || label === 'razor')
return new HtmlWorker()
if (label === 'typescript' || label === 'javascript')
return new TsWorker()
return new EditorWorker()
},
}
})(),
])

// @ts-expect-error
self.MonacoEnvironment = {
getWorker(_: any, label: string) {
if (label === 'json')
return new JsonWorker()
if (label === 'css' || label === 'scss' || label === 'less')
return new CssWorker()
if (label === 'html' || label === 'handlebars' || label === 'razor')
return new HtmlWorker()
if (label === 'typescript' || label === 'javascript')
return new TsWorker()
return new EditorWorker()
},
}
/* __async_injections__ */

monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
noUnusedLocals: false,
noUnusedParameters: false,
allowUnreachableCode: true,
allowUnusedLabels: true,
strict: true,
})
if (getCurrentInstance())
await new Promise<void>(resolve => onMounted(resolve))

export { monaco }
return { monaco }
}
2 changes: 2 additions & 0 deletions packages/slidev/node/plugins/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createSlidesLoader } from './slides'
import { createMonacoLoader, transformMarkdownMonaco } from './monaco'
import { createEntryPlugin } from './entry'
import { resolveOptions, SlidevPluginOptions } from './options'
import { createSetupPlugin } from './setups'

export function ViteSlidevPlugin(options: SlidevPluginOptions = {}): Plugin[] {
const {
Expand Down Expand Up @@ -109,6 +110,7 @@ export function ViteSlidevPlugin(options: SlidevPluginOptions = {}): Plugin[] {
createConfigPlugin(),
createEntryPlugin(slidesOptions),
createSlidesLoader(slidesOptions),
createSetupPlugin(slidesOptions),
createMonacoLoader(),
]
}
49 changes: 49 additions & 0 deletions packages/slidev/node/plugins/setups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { existsSync } from 'fs'
import { join, resolve } from 'path'
import { slash } from '@antfu/utils'
import { Plugin } from 'vite'
import { ResolvedSlidevOptions } from './options'

export function createSetupPlugin({ clientRoot, themeRoot, userRoot }: ResolvedSlidevOptions): Plugin {
const setupEntry = slash(resolve(clientRoot, 'setup'))

return {
name: 'slidev:setup',
enforce: 'pre',
async transform(code, id) {
if (id.startsWith(setupEntry)) {
const name = id.slice(setupEntry.length + 1)
const imports: string[] = []
const injections: string[] = []
const asyncInjections: string[] = []

const setups = [
join(themeRoot, 'setup', name),
join(userRoot, 'setup', name),
]

setups.forEach((path, idx) => {
if (!existsSync(path))
return

imports.push(`import __n${idx} from '/@fs${slash(path)}'`)
injections.push(
`// ${path}`,
`__n${idx}()`,
)
asyncInjections.push(
`// ${path}`,
`await __n${idx}()`,
)
})

code = code.replace('/* __imports__ */', imports.join('\n'))
code = code.replace('/* __injections__ */', injections.join('\n'))
code = code.replace('/* __async_injections__ */', asyncInjections.join('\n'))
return code
}

return null
},
}
}

0 comments on commit 03890a3

Please sign in to comment.