Skip to content

Commit e6a2438

Browse files
authored
Merge pull request #35 from andurils/dev
replace the editor with monaco
2 parents 65579f9 + c75ad57 commit e6a2438

File tree

9 files changed

+204
-40
lines changed

9 files changed

+204
-40
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414

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

17-
Vue SFC REPL as a Vue 2.7 component.
17+
Vue SFC REPL as a Vue 2.7+ component.
1818

1919
## 💻 Simple Usage
2020

2121
```vue
2222
<script setup>
23-
import { Repl } from 'vue-code-view'
24-
import 'vue-code-view/style.css'
23+
import { Repl } from "vue-code-view";
24+
import "vue-code-view/style.css";
2525
</script>
2626
2727
<template>
@@ -43,4 +43,4 @@ VCV is licensed under the terms of the [MIT License](./LICENSE).
4343

4444
Copyright (c) 2021-present Anduril
4545

46-
[preview-ol-v03]:https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/987ab9814e314f92a244fdf6510e6224~tplv-k3u1fbpfcp-watermark.image?
46+
[preview-ol-v03]: https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/987ab9814e314f92a244fdf6510e6224~tplv-k3u1fbpfcp-watermark.image?

index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="UTF-8" />
66
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
77
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8-
<title>Vue SFC Playground</title>
8+
<title>Vue Code View Playground</title>
99
<style>
1010
body {
1111
margin: 0;

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "vue-code-view",
3-
"version": "0.6.0",
3+
"version": "0.6.1",
44
"type": "module",
5-
"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.",
5+
"description": "Vue SFC REPL as a Vue 2.7+ component.",
66
"main": "dist/ssr-stub.js",
7-
"module": "dist/vue-repl.js",
7+
"module": "dist/vue-code-view.js",
88
"files": [
99
"dist"
1010
],
@@ -35,6 +35,7 @@
3535
"codemirror": "^5.62.3",
3636
"fflate": "^0.7.4",
3737
"hash-sum": "^2.0.0",
38+
"monaco-editor": "^0.36.1",
3839
"rimraf": "^4.4.0",
3940
"sucrase": "^3.29.0",
4041
"typescript": "^4.9.3",

src/Repl.vue

+15-22
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<template>
2-
<div ref="vcv" class="vue-repl" :style="calcHeight">
2+
<div ref="vcv" class="vue-repl">
33
<SplitPane :layout="flexDirection">
44
<!-- output render -->
55
<template :slot="outputSlot">
66
<Output :showCompileOutput="props.showCompileOutput" :ssr="!!props.ssr" />
77
</template>
8-
98
<!-- code editor -->
109
<template :slot="editorSlot">
1110
<Editor />
@@ -23,6 +22,7 @@ import { Store, ReplStore, SFCOptions } from "./store";
2322
2423
export interface Props {
2524
store?: Store;
25+
editor?: string;
2626
autoResize?: boolean;
2727
showCompileOutput?: boolean;
2828
showImportMap?: boolean;
@@ -31,29 +31,21 @@ export interface Props {
3131
layout?: string;
3232
ssr?: boolean;
3333
34-
// source: string;
35-
// themeMode?: string; // light||dark,默认 light
36-
// showCode: boolean;
37-
// errorHandler?: Function;
38-
// needAutoResize?: boolean;
39-
// debounceDelay?: number;
40-
// layout: string;
41-
height?: number;
42-
minHeight?: number;
4334
}
4435
4536
const props = withDefaults(defineProps<Props>(), {
4637
store: () => new ReplStore(),
38+
editor: "monaco", // monaco || codemirror
4739
autoResize: true,
4840
showCompileOutput: true,
4941
showImportMap: true,
5042
clearConsole: true,
43+
layout: "left",
5144
ssr: false,
5245
5346
// showCode: false,
5447
// debounceDelay: 300,
55-
layout: "left",
56-
minHeight: 300,
48+
// minHeight: 300,
5749
});
5850
5951
const viewLayout = computed(() =>
@@ -71,22 +63,23 @@ const editorSlot = computed(() =>
7163
const outputSlot = computed(() =>
7264
viewLayout.value == "right" ? "right" : "left"
7365
);
74-
const calcHeight = computed(() => {
75-
let heightSetting = "";
76-
if (props.height && props.height >= 0) {
77-
let vcvHeight =
78-
props.height <= props.minHeight ? props.minHeight : props.height;
79-
heightSetting += ` height:${vcvHeight}px;`;
80-
}
81-
return heightSetting;
82-
});
66+
// const calcHeight = computed(() => {
67+
// let heightSetting = "";
68+
// if (props.height && props.height >= 0) {
69+
// let vcvHeight =
70+
// props.height <= props.minHeight ? props.minHeight : props.height;
71+
// heightSetting += ` height:${vcvHeight}px;`;
72+
// }
73+
// return heightSetting;
74+
// });
8375
8476
8577
props.store.options = props.sfcOptions;
8678
props.store.init();
8779
8880
// provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
8981
provide("store", props.store);
82+
provide("editor", props.editor);
9083
provide("autoresize", props.autoResize);
9184
provide("import-map", toRef(props, "showImportMap"));
9285
provide("clear-console", toRef(props, "clearConsole"));

src/editor/Editor.vue

+27-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
<script setup lang="ts">
33
import FileSelector from "./FileSelector.vue";
44
import CodeMirror from "../codemirror/CodeMirror.vue";
5+
import MonacoEditor from "../monaco/MonacoEditor.vue";
56
import Message from "../Message.vue";
67
import { debounce } from "../utils";
78
import { computed, inject } from "vue";
89
import { Store } from "../store";
910

1011
const store = inject("store") as Store;
12+
const editor = inject("editor") as String;
1113
const onChange = debounce((code: string) => {
1214
store.state.activeFile.code = code;
1315
}, 250);
@@ -20,13 +22,37 @@ const activeMode = computed(() => {
2022
? "css"
2123
: "javascript";
2224
});
25+
26+
const activeMonacoMode = computed(() => {
27+
const { filename } = store.state.activeFile;
28+
29+
if (filename.endsWith(".json")) {
30+
return 'json';
31+
}
32+
if (filename.endsWith(".css") || filename.endsWith(".scss") || filename.endsWith(".less")) {
33+
return 'css';
34+
}
35+
if (filename.endsWith(".html") || filename.endsWith(".htm") || filename.endsWith(".vue")) {
36+
return 'html';
37+
}
38+
if (filename.endsWith(".ts") || filename.endsWith(".tsx")) {
39+
return 'typescript';
40+
}
41+
if (filename.endsWith(".js") || filename.endsWith(".jsx")) {
42+
return 'javascript';
43+
}
44+
return 'html';
45+
});
46+
2347
</script>
2448

2549
<template>
2650
<div class="editor">
2751
<FileSelector />
2852
<div class="editor-container">
29-
<CodeMirror @change="onChange" :value="store.state.activeFile.code" :mode="activeMode" />
53+
<MonacoEditor @change="onChange" :value="store.state.activeFile.code" :mode="activeMonacoMode"
54+
v-if="editor == 'monaco'" />
55+
<CodeMirror @change="onChange" :value="store.state.activeFile.code" :mode="activeMode" v-else />
3056
<Message :err="store.state.errors[0]" />
3157
</div>
3258
</div>

src/monaco/MonacoEditor.vue

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<template>
2+
<div class="editor" ref="el"></div>
3+
</template>
4+
5+
<script setup lang="ts">
6+
import { inject, onBeforeUnmount, onMounted, ref, watchEffect, } from "vue";
7+
import * as monaco from "monaco-editor";
8+
import './userWorker';
9+
10+
export interface Props {
11+
mode?: string;
12+
value?: string;
13+
readonly?: boolean;
14+
// lineNumbers?: boolean;
15+
}
16+
17+
const props = withDefaults(defineProps<Props>(), {
18+
mode: "html", // html css json typescript or [basic-languages]
19+
value: "",
20+
readonly: false,
21+
// lineNumbers: true,
22+
});
23+
24+
const needAutoResize: Boolean = inject("autoresize") as Boolean;
25+
26+
const emit = defineEmits<(e: "change", value: string) => void>();
27+
const el = ref();
28+
let editor: monaco.editor.IStandaloneCodeEditor | null = null;
29+
30+
onMounted(() => {
31+
const defaultEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
32+
fontSize: 14,
33+
formatOnPaste: true,
34+
scrollBeyondLastLine: false,
35+
codeLens: true,
36+
wordWrap: 'bounded',
37+
showFoldingControls: 'mouseover',
38+
// cursorStyle: 'line',
39+
minimap: {
40+
enabled: true,
41+
},
42+
}
43+
44+
// monaco.editor.defineTheme('my-theme', theme)
45+
// monaco.editor.setTheme('my-theme')
46+
47+
editor = monaco.editor.create(el.value, {
48+
value: '', // 内容
49+
language: 'html', // 语言
50+
readOnly: props.readonly, // 只读
51+
automaticLayout: needAutoResize.valueOf(), // 自动调整大小
52+
theme: 'vs-dark', // 主题
53+
...defaultEditorOptions
54+
});
55+
56+
editor.onDidChangeModelContent(() => {
57+
emit("change", editor?.getValue() || '');
58+
});
59+
60+
watchEffect(() => {
61+
const cur = editor?.getValue();
62+
if (props.value !== cur) {
63+
editor?.setValue(props.value);
64+
}
65+
});
66+
67+
// setTimeout(() => {
68+
// editor?.layout();
69+
// }, 50);
70+
71+
// if (needAutoResize) {
72+
// window.addEventListener(
73+
// "resize",
74+
// debounce(() => {
75+
// editor?.layout();
76+
// })
77+
// );
78+
// }
79+
});
80+
81+
onBeforeUnmount(() => {
82+
editor?.dispose();
83+
});
84+
</script>
85+
86+
87+
88+
89+
<style scoped>
90+
.editor {
91+
position: relative;
92+
height: 100%;
93+
width: 100%;
94+
overflow: hidden;
95+
}
96+
</style>

src/monaco/userWorker.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// https://github.com/vitejs/vite/discussions/1791#discussioncomment-321046
2+
// https://github.com/microsoft/monaco-editor/blob/HEAD/docs/integrate-esm.md#using-vite
3+
// https://github.com/microsoft/monaco-editor/tree/main/samples/browser-esm-vite-react
4+
import * as monaco from 'monaco-editor';
5+
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
6+
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
7+
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
8+
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
9+
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
10+
11+
// @ts-ignore
12+
self.MonacoEnvironment = {
13+
getWorker(_: any, label: string) {
14+
if (label === 'json') {
15+
return new jsonWorker();
16+
}
17+
if (label === 'css' || label === 'scss' || label === 'less') {
18+
return new cssWorker();
19+
}
20+
if (label === 'html' || label === 'handlebars' || label === 'razor') {
21+
return new htmlWorker();
22+
}
23+
if (label === 'typescript' || label === 'javascript') {
24+
return new tsWorker();
25+
}
26+
return new editorWorker();
27+
}
28+
};
29+
30+
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);

src/utils.ts

+24-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import { zlibSync, unzlibSync, strToU8, strFromU8 } from "fflate";
22

3-
export function debounce(fn: Function, n = 100) {
4-
let handle: any;
5-
return (...args: any[]) => {
6-
if (handle) clearTimeout(handle);
7-
handle = setTimeout(() => {
8-
fn(...args);
9-
}, n);
10-
};
3+
// export function debounce(fn: Function, n = 100) {
4+
// let handle: any;
5+
// return (...args: any[]) => {
6+
// if (handle) clearTimeout(handle);
7+
// handle = setTimeout(() => {
8+
// fn(...args);
9+
// }, n);
10+
// };
11+
// }
12+
13+
export function debounce<T extends (...args: any[]) => any>(
14+
fn: T,
15+
delay: number = 300
16+
): T {
17+
let prevTimer: number | null = null
18+
return ((...args: any[]) => {
19+
if (prevTimer) {
20+
clearTimeout(prevTimer)
21+
}
22+
prevTimer = window.setTimeout(() => {
23+
fn(...args)
24+
prevTimer = null
25+
}, delay)
26+
}) as T
1127
}
1228

1329
export function utoa(data: string): string {

tsconfig.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"esModuleInterop": true,
1717
"removeComments": false,
1818
"lib": ["ESNext", "DOM"],
19-
"rootDir": "."
19+
"rootDir": ".",
20+
"composite": true,
21+
"allowSyntheticDefaultImports": true
2022
// "isolatedModules": true,
2123
// "skipLibCheck": true,
2224
// "noEmit": true

0 commit comments

Comments
 (0)