Working in JSDOM or Node.js natively

Just curious if anyone has tried getting codemirror running inside of a virtual dom environment like JSDOM. I’m generating server side html with React and then bootstrapping codemirror in the browser, but this leads to a rather long and unpleasant flash of unstyled text area. I’d like to be able to generate a full (static) DOM tree with codemirror so that things are at least styled before they are functional. The current attempt in JSDOM leads to: TypeError: Object [ BODY ] has no method ‘createTextRange’, which I’m looking into.

Alright, injecting this code is enough to get it to not error out:

        document.createRange = function() {
            return {
                setEnd: function(){},
                setStart: function(){},
                getBoundingClientRect: function(){
                    return {right: 0};
                }
            }
        };

It still doesn’t render correctly, but I’ll keep working on it. I see PhantomJS is how you’re running tests, but I think it will be too heavy for my use case. I may try that out as well.

1 Like

Hey man!, were you able to run it in the backend or not? I’m getting an error:

/Users/danielzamorano/workspace/basepath/node_modules/codemirror/lib/codemirror.js:25
var gecko = /gecko/\d/i.test(navigator.userAgent);

I’m also using JSDOM

Nevermind I found the solution:

var expect = require(‘chai’).expect;
var jsdom = require(‘node-jsdom’);
var sinon = require(‘sinon’);

// virtual DOM environment for codemirror
global.navigator = ‘gecko’;
global.document = jsdom.jsdom();
global.window = global.document.defaultView;

var markdownEditor = require(’…/…/…/app/Authoring/Edit/markdown_editor.js’);

I used boucher’s approach and that’s how initialization looks for me (I believe you shoud define createTextRange, not just createRange):

    import jsdom from 'jsdom'
    const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
    global.document = doc
    global.window = doc.defaultView
    global.document.body.createTextRange = function() {
        return {
            setEnd: function(){},
            setStart: function(){},
            getBoundingClientRect: function(){
                return {right: 0};
            },
            getClientRects: function(){
                return {
                    length: 0,
                    left: 0,
                    right: 0
                }
            }
        }
    }

Still see this warning document.body.createTextRange is not a function

I was able to implement a similar polyfill by instead putting it on the Document prototype
Something like the following:

const polyfills = {
  createRange() {
    // @ts-ignore
    Document.prototype.createRange = function () {
      return {
        setEnd: function () {},
        setStart: function () {},
        getBoundingClientRect: function () {
          return { right: 0 }
        },
        getClientRects: function () {
          return {
            length: 0,
            left: 0,
            right: 0,
          }
        },
      }
    }
  },
}
polyfills.createRange()

This was useful for ProseMirror, but something similar may be used to fix this in CodeMirror as well.

Trying to spawn a basic setup in codemirror 6:

import("./haverbeke_2020_codemirror.js").then(({EditorState,EditorView})=>
new EditorView({state:EditorState.create({doc:""})}));

(the import resolution doesn’t even matter actually) and I get:

Uncaught ReferenceError: btoa is not defined
    at underline (VM1218 haverbeke_2020_codemirror.js:15010)
    at VM1218 haverbeke_2020_codemirror.js:15034
    at ModuleJob.run (VM95 module_job:146)
    at async Loader.import (VM93 loader:165)

the throwing function looks like this:

function underline(color) {
    let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
    <path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width=".7"/>
  </svg>`;
    return `url('data:image/svg+xml;base64,${btoa(svg)}')`;
}

btoa seems easily replaceable with Buffer.from,
should this be a github issue, or am I missing
something that there can be done?

My intention is basically to maintain a server-side
state of a document during a collaborative session.

The btoa thing was fixed in this patch and should no longer be present in 0.14.0

Oh right, I just had my package.json point to an earlier version. btoa went away, now with the same basic setup (and using JSDOM to set a window and document object on globalThis), I am getting

Uncaught ReferenceError: MutationObserver is not defined
    at new DOMObserver (VM1154 haverbeke_2020_codemirror.js:8874)
    at new EditorView (VM1154 haverbeke_2020_codemirror.js:9334)

the source looks like this:

class DOMObserver
{constructor(view, onChange, onScrollChanged) {
 this.view = view;
 this.onChange = onChange;
 this.onScrollChanged = onScrollChanged;
 [...]
 this.dom = view.contentDOM;
 this.observer = new MutationObserver(mutations=>{})
 [...]

Has anyone managed to get around MutationObserver?
Due to my mentioned use case,
I’m only going to run transactions against this view/state,
not observed mutations so I could probably do without this part.

…actually, nevermind I realised I don’t need the View for this.
now I just need to figure out how to shuffle transactions on a State.

You can run transactions without having a view—@codemirror/next/state runs fine in node without JSDOM. But creating an actual view instance there is probably not going to work in any meaningful way.