Skip to content

replace the editor with monaco #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 3, 2023
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@

English | [简体中文](./README.zh-CN.md)

Vue SFC REPL as a Vue 2.7 component.
Vue SFC REPL as a Vue 2.7+ component.

## 💻 Simple Usage

```vue
<script setup>
import { Repl } from 'vue-code-view'
import 'vue-code-view/style.css'
import { Repl } from "vue-code-view";
import "vue-code-view/style.css";
</script>

<template>
Expand All @@ -43,4 +43,4 @@ VCV is licensed under the terms of the [MIT License](./LICENSE).

Copyright (c) 2021-present Anduril

[preview-ol-v03]:https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/987ab9814e314f92a244fdf6510e6224~tplv-k3u1fbpfcp-watermark.image?
[preview-ol-v03]: https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/987ab9814e314f92a244fdf6510e6224~tplv-k3u1fbpfcp-watermark.image?
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue SFC Playground</title>
<title>Vue Code View Playground</title>
<style>
body {
margin: 0;
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "vue-code-view",
"version": "0.6.0",
"version": "0.6.1",
"type": "module",
"description": "A Vue 2 component like Vue SFC REPL `@vue/repl` : u can edit, run and preview the code effect display in real time on the web page.",
"description": "Vue SFC REPL as a Vue 2.7+ component.",
"main": "dist/ssr-stub.js",
"module": "dist/vue-repl.js",
"module": "dist/vue-code-view.js",
"files": [
"dist"
],
Expand Down Expand Up @@ -35,6 +35,7 @@
"codemirror": "^5.62.3",
"fflate": "^0.7.4",
"hash-sum": "^2.0.0",
"monaco-editor": "^0.36.1",
"rimraf": "^4.4.0",
"sucrase": "^3.29.0",
"typescript": "^4.9.3",
Expand Down
37 changes: 15 additions & 22 deletions src/Repl.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<template>
<div ref="vcv" class="vue-repl" :style="calcHeight">
<div ref="vcv" class="vue-repl">
<SplitPane :layout="flexDirection">
<!-- output render -->
<template :slot="outputSlot">
<Output :showCompileOutput="props.showCompileOutput" :ssr="!!props.ssr" />
</template>

<!-- code editor -->
<template :slot="editorSlot">
<Editor />
Expand All @@ -23,6 +22,7 @@ import { Store, ReplStore, SFCOptions } from "./store";

export interface Props {
store?: Store;
editor?: string;
autoResize?: boolean;
showCompileOutput?: boolean;
showImportMap?: boolean;
Expand All @@ -31,29 +31,21 @@ export interface Props {
layout?: string;
ssr?: boolean;

// source: string;
// themeMode?: string; // light||dark,默认 light
// showCode: boolean;
// errorHandler?: Function;
// needAutoResize?: boolean;
// debounceDelay?: number;
// layout: string;
height?: number;
minHeight?: number;
}

const props = withDefaults(defineProps<Props>(), {
store: () => new ReplStore(),
editor: "monaco", // monaco || codemirror
autoResize: true,
showCompileOutput: true,
showImportMap: true,
clearConsole: true,
layout: "left",
ssr: false,

// showCode: false,
// debounceDelay: 300,
layout: "left",
minHeight: 300,
// minHeight: 300,
});

const viewLayout = computed(() =>
Expand All @@ -71,22 +63,23 @@ const editorSlot = computed(() =>
const outputSlot = computed(() =>
viewLayout.value == "right" ? "right" : "left"
);
const calcHeight = computed(() => {
let heightSetting = "";
if (props.height && props.height >= 0) {
let vcvHeight =
props.height <= props.minHeight ? props.minHeight : props.height;
heightSetting += ` height:${vcvHeight}px;`;
}
return heightSetting;
});
// const calcHeight = computed(() => {
// let heightSetting = "";
// if (props.height && props.height >= 0) {
// let vcvHeight =
// props.height <= props.minHeight ? props.minHeight : props.height;
// heightSetting += ` height:${vcvHeight}px;`;
// }
// return heightSetting;
// });


props.store.options = props.sfcOptions;
props.store.init();

// provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
provide("store", props.store);
provide("editor", props.editor);
provide("autoresize", props.autoResize);
provide("import-map", toRef(props, "showImportMap"));
provide("clear-console", toRef(props, "clearConsole"));
Expand Down
28 changes: 27 additions & 1 deletion src/editor/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
<script setup lang="ts">
import FileSelector from "./FileSelector.vue";
import CodeMirror from "../codemirror/CodeMirror.vue";
import MonacoEditor from "../monaco/MonacoEditor.vue";
import Message from "../Message.vue";
import { debounce } from "../utils";
import { computed, inject } from "vue";
import { Store } from "../store";

const store = inject("store") as Store;
const editor = inject("editor") as String;
const onChange = debounce((code: string) => {
store.state.activeFile.code = code;
}, 250);
Expand All @@ -20,13 +22,37 @@ const activeMode = computed(() => {
? "css"
: "javascript";
});

const activeMonacoMode = computed(() => {
const { filename } = store.state.activeFile;

if (filename.endsWith(".json")) {
return 'json';
}
if (filename.endsWith(".css") || filename.endsWith(".scss") || filename.endsWith(".less")) {
return 'css';
}
if (filename.endsWith(".html") || filename.endsWith(".htm") || filename.endsWith(".vue")) {
return 'html';
}
if (filename.endsWith(".ts") || filename.endsWith(".tsx")) {
return 'typescript';
}
if (filename.endsWith(".js") || filename.endsWith(".jsx")) {
return 'javascript';
}
return 'html';
});

</script>

<template>
<div class="editor">
<FileSelector />
<div class="editor-container">
<CodeMirror @change="onChange" :value="store.state.activeFile.code" :mode="activeMode" />
<MonacoEditor @change="onChange" :value="store.state.activeFile.code" :mode="activeMonacoMode"
v-if="editor == 'monaco'" />
<CodeMirror @change="onChange" :value="store.state.activeFile.code" :mode="activeMode" v-else />
<Message :err="store.state.errors[0]" />
</div>
</div>
Expand Down
96 changes: 96 additions & 0 deletions src/monaco/MonacoEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<template>
<div class="editor" ref="el"></div>
</template>

<script setup lang="ts">
import { inject, onBeforeUnmount, onMounted, ref, watchEffect, } from "vue";
import * as monaco from "monaco-editor";
import './userWorker';

export interface Props {
mode?: string;
value?: string;
readonly?: boolean;
// lineNumbers?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
mode: "html", // html css json typescript or [basic-languages]
value: "",
readonly: false,
// lineNumbers: true,
});

const needAutoResize: Boolean = inject("autoresize") as Boolean;

const emit = defineEmits<(e: "change", value: string) => void>();
const el = ref();
let editor: monaco.editor.IStandaloneCodeEditor | null = null;

onMounted(() => {
const defaultEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
fontSize: 14,
formatOnPaste: true,
scrollBeyondLastLine: false,
codeLens: true,
wordWrap: 'bounded',
showFoldingControls: 'mouseover',
// cursorStyle: 'line',
minimap: {
enabled: true,
},
}

// monaco.editor.defineTheme('my-theme', theme)
// monaco.editor.setTheme('my-theme')

editor = monaco.editor.create(el.value, {
value: '', // 内容
language: 'html', // 语言
readOnly: props.readonly, // 只读
automaticLayout: needAutoResize.valueOf(), // 自动调整大小
theme: 'vs-dark', // 主题
...defaultEditorOptions
});

editor.onDidChangeModelContent(() => {
emit("change", editor?.getValue() || '');
});

watchEffect(() => {
const cur = editor?.getValue();
if (props.value !== cur) {
editor?.setValue(props.value);
}
});

// setTimeout(() => {
// editor?.layout();
// }, 50);

// if (needAutoResize) {
// window.addEventListener(
// "resize",
// debounce(() => {
// editor?.layout();
// })
// );
// }
});

onBeforeUnmount(() => {
editor?.dispose();
});
</script>




<style scoped>
.editor {
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
30 changes: 30 additions & 0 deletions src/monaco/userWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// https://github.com/vitejs/vite/discussions/1791#discussioncomment-321046
// https://github.com/microsoft/monaco-editor/blob/HEAD/docs/integrate-esm.md#using-vite
// https://github.com/microsoft/monaco-editor/tree/main/samples/browser-esm-vite-react
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';

// @ts-ignore
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();
}
};

monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
32 changes: 24 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { zlibSync, unzlibSync, strToU8, strFromU8 } from "fflate";

export function debounce(fn: Function, n = 100) {
let handle: any;
return (...args: any[]) => {
if (handle) clearTimeout(handle);
handle = setTimeout(() => {
fn(...args);
}, n);
};
// export function debounce(fn: Function, n = 100) {
// let handle: any;
// return (...args: any[]) => {
// if (handle) clearTimeout(handle);
// handle = setTimeout(() => {
// fn(...args);
// }, n);
// };
// }

export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number = 300
): T {
let prevTimer: number | null = null
return ((...args: any[]) => {
if (prevTimer) {
clearTimeout(prevTimer)
}
prevTimer = window.setTimeout(() => {
fn(...args)
prevTimer = null
}, delay)
}) as T
}

export function utoa(data: string): string {
Expand Down
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"esModuleInterop": true,
"removeComments": false,
"lib": ["ESNext", "DOM"],
"rootDir": "."
"rootDir": ".",
"composite": true,
"allowSyntheticDefaultImports": true
// "isolatedModules": true,
// "skipLibCheck": true,
// "noEmit": true
Expand Down