Skip to content

Commit 95d1703

Browse files
author
李磊
committed
feat: vite 使用 monaco-Editor
1 parent 6d05cce commit 95d1703

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

apps/docs/.vitepress/config/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const zh = defineConfig({
7979
{ text: 'uni-app 自动更新', link: '/blog/autoUpdate' },
8080
{ text: 'mqtt class', link: '/blog/mqtt' },
8181
{ text: 'Vue大数据量渲染优化', link: '/blog/vueLargeDataRenderingOptimization' },
82+
{ text: 'vite 使用 monaco-Editor', link: '/blog/monacoEditor' },
8283
],
8384
},
8485
],

apps/docs/zh/blog/monacoEditor.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# 使用 Monaco Editor 的注意事项
2+
3+
在使用 `monaco-editor` 时,尤其是在基于 Vite 的项目中,可能会遇到 `worker` 加载失败的问题。这是因为 `monaco-editor` 默认使用 Webpack 的方式加载 `worker`,而 Vite 的打包机制与 Webpack 不同,需要手动配置 `worker`
4+
5+
## 解决方案
6+
7+
为了让 `monaco-editor` 正常工作,我们需要显式地引入各个语言的 `worker` 文件,并通过 `MonacoEnvironment``getWorker` 方法进行配置。以下是具体的处理方式:
8+
9+
### 引入 `worker`
10+
11+
我们通过以下方式引入 `worker` 文件:
12+
13+
```typescript
14+
// @ts-ignore
15+
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
16+
// @ts-ignore
17+
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
18+
// @ts-ignore
19+
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
20+
// @ts-ignore
21+
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
22+
// @ts-ignore
23+
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
24+
```
25+
26+
通过 `?worker` 的方式引入文件是 Vite 的特性,它会将这些文件打包为独立的 `worker` 文件,并返回一个可以用于实例化的 `Worker` 类。
27+
28+
### 配置 `MonacoEnvironment`
29+
30+
我们需要通过 `self.MonacoEnvironment` 来覆盖默认的 `getWorker` 方法,根据语言标签返回对应的 `worker` 实例:
31+
32+
```typescript
33+
// eslint-disable-next-line no-restricted-globals
34+
self.MonacoEnvironment = {
35+
getWorker(_, label) {
36+
if (label === 'json') {
37+
return new jsonWorker();
38+
}
39+
if (label === 'css' || label === 'scss' || label === 'less') {
40+
return new cssWorker();
41+
}
42+
if (label === 'html' || label === 'handlebars' || label === 'razor') {
43+
return new htmlWorker();
44+
}
45+
if (label === 'typescript' || label === 'javascript') {
46+
return new tsWorker();
47+
}
48+
return new editorWorker();
49+
},
50+
};
51+
```
52+
53+
### 为什么需要这一步?
54+
55+
`monaco-editor` 的默认行为是动态加载 `worker` 文件,但在 Vite 中,动态加载的路径可能会出错,导致 `worker` 无法找到。通过手动引入并配置 `getWorker` 方法,我们可以确保 `worker` 文件被正确加载。
56+
57+
## 总结
58+
59+
通过上述方式,我们可以在 Vite 项目中正常使用 `monaco-editor`,并支持多种语言的代码编辑功能。完整的代码示例可以参考以下内容:
60+
61+
```vue
62+
<template>
63+
<div ref="editorContainer" class="editor-content" :class="[showBorder ? 'border-class' : '']" />
64+
</template>
65+
66+
<script setup lang="ts">
67+
import * as monaco from 'monaco-editor';
68+
// @ts-ignore
69+
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
70+
// @ts-ignore
71+
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
72+
// @ts-ignore
73+
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
74+
// @ts-ignore
75+
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
76+
// @ts-ignore
77+
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
78+
79+
const props = defineProps<{
80+
modelValue?: string;
81+
language?: string;
82+
theme?: string;
83+
readOnly?: boolean;
84+
showBorder?: boolean;
85+
}>();
86+
const emit = defineEmits<{
87+
(e: 'update:modelValue', value: string): void;
88+
(e: 'change', value: string): void;
89+
}>();
90+
// eslint-disable-next-line no-restricted-globals
91+
self.MonacoEnvironment = {
92+
getWorker(_, label) {
93+
if (label === 'json') {
94+
// eslint-disable-next-line new-cap
95+
return new jsonWorker();
96+
}
97+
if (label === 'css' || label === 'scss' || label === 'less') {
98+
// eslint-disable-next-line new-cap
99+
return new cssWorker();
100+
}
101+
if (label === 'html' || label === 'handlebars' || label === 'razor') {
102+
// eslint-disable-next-line new-cap
103+
return new htmlWorker();
104+
}
105+
if (label === 'typescript' || label === 'javascript') {
106+
// eslint-disable-next-line new-cap
107+
return new tsWorker();
108+
}
109+
// eslint-disable-next-line new-cap
110+
return new editorWorker();
111+
},
112+
};
113+
const editorContainer = ref<HTMLElement | null>(null);
114+
const editor = ref<monaco.editor.IStandaloneCodeEditor | null>(null);
115+
const currentLanguage = computed(() => {
116+
return props.language || 'json';
117+
});
118+
119+
// Initialize Monaco Editor
120+
onMounted(() => {
121+
if (editorContainer.value) {
122+
editor.value = monaco.editor.create(editorContainer.value, {
123+
value: props.modelValue || '',
124+
language: currentLanguage.value,
125+
theme: props.theme || 'vs',
126+
automaticLayout: true,
127+
minimap: {
128+
enabled: false,
129+
},
130+
scrollBeyondLastLine: false,
131+
fontSize: 14,
132+
lineNumbers: 'on',
133+
readOnly: props.readOnly || false,
134+
tabSize: 2,
135+
wordWrap: 'on',
136+
folding: true,
137+
lineDecorationsWidth: 10,
138+
lineNumbersMinChars: 3,
139+
renderLineHighlight: 'all',
140+
scrollbar: {
141+
vertical: 'visible',
142+
horizontal: 'visible',
143+
useShadows: false,
144+
verticalScrollbarSize: 10,
145+
horizontalScrollbarSize: 10,
146+
},
147+
});
148+
149+
// Handle editor content changes
150+
toRaw(editor.value).onDidChangeModelContent(() => {
151+
const value = toRaw(editor.value)?.getValue() || '';
152+
emit('update:modelValue', value);
153+
emit('change', value);
154+
});
155+
}
156+
});
157+
158+
// Watch for language changes
159+
watch(currentLanguage, (newLanguage) => {
160+
if (editor.value) {
161+
monaco.editor.setModelLanguage(toRaw(editor.value).getModel()!, newLanguage);
162+
}
163+
});
164+
165+
// Watch for value changes from parent
166+
watch(
167+
() => props.modelValue,
168+
(newValue) => {
169+
if (editor.value && newValue !== toRaw(editor.value)?.getValue()) {
170+
toRaw(editor.value)?.setValue(newValue || '');
171+
}
172+
},
173+
);
174+
175+
// Watch for readOnly changes
176+
watch(
177+
() => props.readOnly,
178+
(newReadOnly) => {
179+
if (editor.value) {
180+
editor.value.updateOptions({ readOnly: newReadOnly });
181+
}
182+
},
183+
);
184+
185+
// Watch for theme changes
186+
watch(
187+
() => props.theme,
188+
(newTheme) => {
189+
if (editor.value && newTheme) {
190+
monaco.editor.setTheme(newTheme);
191+
}
192+
},
193+
);
194+
195+
// Cleanup
196+
onBeforeUnmount(() => {
197+
if (editor.value) {
198+
toRaw(editor.value)?.dispose?.();
199+
}
200+
});
201+
</script>
202+
203+
<style scoped>
204+
.editor-content {
205+
width: 100%;
206+
height: 100%;
207+
}
208+
.editor-content.border-class {
209+
border: 1px solid var(--bmos-first-level-border-color);
210+
border-radius: 4px;
211+
}
212+
</style>
213+
```

0 commit comments

Comments
 (0)