solid-codemirror provides a set of utilities to make CodeMirror6 integration easier for SolidJS.
This library was initially born to be the entry of the SolidJS hackathon, but has become the core editor of CodeImage.
# pnpm
> pnpm add solid-codemirror
# or yarn
> yarn add solid-codemirror
# or npm
> npm i solid-codemirror
Note This library depends on @codemirror/state and @codemirror/view v6.0.0. These libraries are flagged as ** peerDependencies**. It's recommended to manually install both of them to have more control and flexibility for implementation
Warning You may encounter this error using Vite
Error: Unrecognized extension value in extension set ([object Object]).
This sometimes happens because multipleinstances of @codemirror/state are loaded,
breaking instanceof checks.
If @codemirror/state and @codemirror/view are not working even if their version dont't mismatch, you can try to add the
following to your vite.config.{js,ts}
file:
export default defineConfig({
// Your configuration
optimizeDeps: {
// Add both @codemirror/state and @codemirror/view to included deps to optimize
include: ['@codemirror/state', '@codemirror/view'],
}
})
solid-codemirror | @codemirror/state | @codemirror/view | solid-js |
---|---|---|---|
>=1.3.0 | ^6.2.0 | ^6.12.0 | ^1.7.0 |
>=1 < 1.3.0 | ^6.0.0 | ^6.0.0 | ^1.6.0 |
First, we need to create a new CodeMirror instance through the createCodeMirror
function. Next, the given ref
object must be attached to a DOM Element in order to initialize the EditorView
instance with his own EditorState
.
import { createCodeMirror } from "solid-codemirror";
import { createSignal, onMount } from "solid-js";
export const App = () => {
const { editorView, ref: editorRef } = createCodeMirror({
/**
* The initial value of the editor
*/
value: "console.log('hello world!')",
/**
* Fired whenever the editor code value changes.
*/
onValueChange: (value) => console.log("value changed", value),
/**
* Fired whenever a change occurs to the document, every time the view updates.
*/
onModelViewUpdate: (modelView) => console.log("modelView updated", modelView),
/**
* Fired whenever a transaction has been dispatched to the view.
* Used to add external behavior to the transaction [dispatch function](https://codemirror.net/6/docs/ref/#view.EditorView.dispatch) for this editor view, which is the way updates get routed to the view
*/
onTransactionDispatched: (tr: Transaction, view: EditorView) => console.log("Transaction", tr)
});
return <div ref={editorRef} />;
};
The createCodeMirror
function is the main function of solid-codemirror
to start creating your editor. It exposes the
following properties:
Property | Description |
---|---|
ref |
The HTMLElement object which will be attached to the EditorView instance |
editorView |
The current EditorView instance of CodeMirror. Will be null as default, and it will be valued when the given ref is mounted in the DOM |
createExtension |
A function to create a new extension for CodeMirror using compartments. It's a shortand for the createCompartmentExtension helper which automatically attaches the right EditorView instance |
https://codemirror.net/docs/guide/
CodeMirror is set up as a collection of separate modules that, together, provide a full-featured text and code editor. On the bright side, this means that you can pick and choose which features you need, and even replace core functionality with a custom implementation if you need to. On the less bright side, this means that setting up an editor requires you to put together a bunch of pieces.
As the official documentation says, CodeMirror6 is modular.
solid-codemirror will not be a replacement for all the modules of CodeMirror6, but will try to provide only the primitives necessary to integrate them in SolidJS.
Each extension which you need to develop your editor must be installed individually and integrated individually.
import { createCodeMirror, createEditorFocus } from 'solid-codemirror';
import { createSignal } from 'solid-js';
const { editorView } = createCodeMirror();
const [readOnly, setReadOnly] = createSignal(true);
const {
focused,
setFocused
} = createEditorFocus(editorView, (focused) => console.log('focus changed', focused));
// Focus
setFocused(true);
// Blur
setFocused(false);
After the CodeMirror editor is mounted, you can update its readonly state using the
createReadonlyEditor
function that accept the editor view and the readOnly accessor.
Note Updating the accessor, the editor readOnly state will be updated automatically;
import { createCodeMirror, createEditorReadonly } from 'solid-codemirror';
import { createSignal } from 'solid-js';
function App() {
const { ref, editorView } = createCodeMirror();
const [readOnly, setReadOnly] = createSignal(true);
createEditorReadonly(editorView, readonly);
return <div ref={ref} />
}
After CodeMirror editor is mounted, you can control the code state using the
createEditorControlledValue
.
Note The value of the editor is already memoized
import { createCodeMirror, createEditorControlledValue } from 'solid-codemirror';
import { createSignal } from 'solid-js';
function App() {
const [code, setCode] = createSignal("console.log('hello world!')");
const { ref, editorView } = createCodeMirror({ onValueChange: setCode });
createEditorControlledValue(editorView, code);
// Update code after 2.5s
setTimeout(() => {
setCode("console.log('updated!')");
}, 2500);
return <div ref={ref} />
}
After CodeMirror editor is mounted, you can handle custom extensions thanks to the
createExtension
function. It both accept an Extension
or Accessor<Extension | undefined>
then
it will be automatically reconfigured when the extension changes. Otherwise, you can manually reconfigure them using
the returned reconfigure
function.
For more details, see the official documentation about compartments.
import { createCodeMirror } from 'solid-codemirror';
import { createSignal } from 'solid-js';
import { EditorView, lineNumbers } from '@codemirror/view';
function App() {
const [code, setCode] = createSignal("console.log('hello world!')");
const [showLineNumber, setShowLineNumber] = createSignal(true);
const { ref, createExtension } = createCodeMirror({ onValueChange: setCode });
const theme = EditorView.theme({
'&': {
background: 'red'
}
});
// Add a static custom theme
createExtension(theme);
// Toggle extension
createExtension(() => showLineNumber() ? lineNumbers() : []);
// Remove line numbers after 2.5s
setTimeout(() => {
setShowLineNumber(false);
}, 2500);
return <div ref={ref} />
}
Sometimes you may need to better split your custom extensions in order to reduce the initial bundle size. This is the case where you need to use dynamic imports in order to resolve the module in lazy loading.
// ✅ 1. Remove the static import
// import { largeExtension } from './large-extension';
import { createLazyCompartmentExtension } from './createLazyCompartmentExtension';
import { Show } from 'solid-js';
function App() {
const [code, setCode] = createSignal("console.log('hello world!')");
const { ref, createExtension } = createCodeMirror({ onValueChange: setCode });
// ✅ 2. Call the helper providing a Promise<Extension>
// The extension will be configured only after the Promise resolves
const largeExt = createLazyCompartmentExtension(
() => import('./large-extension').then(res => res.largeExtension)
);
return (
<div>
<div ref={ref} />
{/*✅ 3. You can read the pending state of the Promise*/}
<Show when={largeExt.loading}>
Loading...
</Show>
</div>
)
}
// WIP
You can also view an advanced implementation of solid-codemirror
through CodeImage
implementation
Licensed under the MIT License.