Using Custom Text Editor in Sandpack
Sandpack React is bundled with the Codemirror editor. Sometimes, you want to use a different text editor. Here’s how to do it.
Code samples are written for Vue, although the general principle applies to other frameworks as well.
Sandpack Client
We will be using Sandpack Client. It’s a low-level library, so we will have full control over what editor we can use.
I’ve covered the basics of Sandpack Client in one of the previous articles. It is essentially a bare-bones sandboxing engine stripped out of any React-specific stuff.
First, install Sandpack Client:
npm i @codesandbox/sandpack-client
Create a new file with a Box
component, with template containing an <iframe>
element and Sandpack initialization inside an onMounted
hook:
<template>
<div class="sandbox">
<iframe ref="iframeEl" />
</div>
</template>
<script setup>
import { SandpackClient } from '@codesandbox/sandpack-client';
import { onMounted, ref } from 'vue';
const iframeEl = ref(null);
let client;
onMounted(() => {
client = new SandpackClient(iframeEl.value, {
files: {
'/index.html': {
code: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo</title>
</head>
<body>
<h1>Hello, Sandpack</h1>
</body>
</html>
`,
},
},
dependencies: {},
entry: '/index.html',
});
});
</script>
<style scoped>
iframe {
width: 500px;
height: 300px;
border: 1px solid #333;
}
</style>
Basic editor
We will first add a basic <textarea>
element to control the file source.
First, let’s move the source into a separate state variable so that we can update it later:
const code = ref(`...`);
onMounted(() => {
client = new SandpackClient(iframeEl.value, {
files: {
'/index.html': {
code: code.value,
},
},
dependencies: {},
entry: '/index.html',
});
});
Then, add a <textarea>
with the code
model:
<textarea v-model="code" />
Finally, update Sandpack preview based on code
change:
watch(code, (newCode) => {
client.updatePreview({
files: {
'/index.html': {
code: newCode,
},
},
dependencies: {},
entry: '/index.html',
});
});
You can style the textarea input as well.
The final code would look like this:
<template>
<div class="sandbox">
<textarea v-model="code" />
<iframe ref="iframeEl" />
</div>
</template>
<script setup>
import { SandpackClient } from '@codesandbox/sandpack-client';
import { onMounted, ref, watch } from 'vue';
const iframeEl = ref(null);
const code = ref(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo</title>
</head>
<body>
<h1>Hello, Sandpack</h1>
</body>
</html>`);
let client;
onMounted(() => {
client = new SandpackClient(iframeEl.value, {
files: {
'/index.html': {
code: code.value,
},
},
dependencies: {},
entry: '/index.html',
});
});
watch(code, (newCode) => {
client.updatePreview({
files: {
'/index.html': {
code: newCode,
},
},
dependencies: {},
entry: '/index.html',
});
});
</script>
<style scoped>
.sandbox {
display: flex;
}
textarea {
width: 500px;
height: 300px;
border: 1px solid #333;
}
iframe {
width: 500px;
height: 300px;
border: 1px solid #333;
}
</style>
Monaco editor
Now we will integrate Monaco into Sandpack.
Install the package:
npm i monaco-editor
Update the component template:
<template>
<div class="sandbox">
<div class="editor-wrapper" ref="monacoEl" />
<iframe ref="iframeEl" />
</div>
</template>
Import and initialize the editor:
import * as monaco from 'monaco-editor';
const monacoEl = ref(null);
onMounted(() => {
const editor = monaco.editor.create(monacoEl.value, {
value: code.value,
language: 'javascript',
});
editor.onDidChangeModelContent(() => {
const newCode = editor.getModel().getValue();
code.value = newCode;
});
// ...
});
Update the styling:
.sandbox {
display: flex;
}
.editor-wrapper {
width: 500px;
height: 300px;
border: 1px solid #333;
}
iframe {
width: 500px;
height: 300px;
border: 1px solid #333;
}
Here’s the full code:
<template>
<div class="sandbox">
<div class="editor-wrapper" ref="monacoEl" />
<iframe ref="iframeEl" />
</div>
</template>
<script setup>
import { SandpackClient } from '@codesandbox/sandpack-client';
import { onMounted, ref, watch } from 'vue';
import * as monaco from 'monaco-editor';
const monacoEl = ref(null);
const iframeEl = ref(null);
const code = ref(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo</title>
</head>
<body>
<h1>Hello, Sandpack</h1>
</body>
</html>`);
let client;
onMounted(() => {
const editor = monaco.editor.create(monacoEl.value, {
value: code.value,
language: 'javascript',
});
editor.onDidChangeModelContent(() => {
const newCode = editor.getModel().getValue();
code.value = newCode;
});
client = new SandpackClient(iframeEl.value, {
files: {
'/index.html': {
code: code.value,
},
},
dependencies: {},
entry: '/index.html',
});
});
watch(code, (newCode) => {
client.updatePreview({
files: {
'/index.html': {
code: newCode,
},
},
dependencies: {},
entry: '/index.html',
});
});
</script>
<style scoped>
.sandbox {
display: flex;
}
.editor-wrapper {
width: 500px;
height: 300px;
border: 1px solid #333;
}
iframe {
width: 500px;
height: 300px;
border: 1px solid #333;
}
</style>
You should have a working integration by now. From there, you can customize the editor by changing the appearance, adding custom commands, and so on.