Various CDNs offer support for bundling and/or pre-ackaging NPM modules.

This gist is an attempt to demonstrate each of these CDNs and CodeMirror.

Status Summary

CDN Status Live Example
esm.sh ✔️ Works! esmsh.html
JSDelivr ✔️ Works! jsdelivr.html
Skypack ❌ Does not work skypack.html
UNPKG ❌ Does not work unpkg.html

The Test

The test is simple: load CodeMirror from a CDN and see if it works.

The test is performed by loading the following HTML file from each CDN:

<main></main>
<script type="module">
  import {basicSetup, EditorView} from 'https://${cdn}/codemirror@6.0.1'
  import {javascript} from 'https://${cdn}/@codemirror/lang-javascript@6.1.7'

  let editor = new EditorView({
    doc: document.querySelector('script:not([src])').innerText,
    extensions: [basicSetup, javascript()],
    parent: document.querySelector('main'),
  });
</script>

And the expected result is:

Screenshot of expected result

That is to say, the CodeMirror editor should load and display the JavaScript code, with syntax highlighting. The actual URL might differ slightly between CDNs.

Issues

For CDN versions of CodeMirror to work, some things have to go right.

  1. The CDN has to be able to bundle/compile NPM modules.
  2. The CND has to serve the module with the correct MIME type.
  3. The CDN has to bundle/compile the CodeMirror module correctly.

Not all CDNs support all of these requirements by default. Several CDNs do provide parameters to help resolve these issues. Still, some CDNs do not appear to resolve all issues.

Bundle NPM modules

The first issue is that the CDN has to be able to bundle NPM modules.

If basic bundling is not provided, there is no need to continue, as the code must be bundled before it can be used in the browser.

Bundle correctly

But just bundling is not enough. The bundling has to be done correctly.

Various things can go wrong, but the most common issue (mentioned in the CodeMirror forum by the author of CodeMirror) is that:

A lot of stuff in CodeMirror doesn’t work when loading through a CDN, because those tend to load multiple versions of packages that are dependended on from multiple other packages.

This issue will trigger an error from CodeMirror itself:

Unrecognized extension value in extension set. This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.

(see the related source code).

Serve the correct MIME type

The specification that defines how scripts should be processed states that the content-type header has to be one of the JavaScript MIME types.

So, for instance, if the script is served as Common JS, the MIME type will be application/node, and the script will not be loaded by the browser but trigger an error:

Screenshot of the error

Results

esm.sh

No issues, no surprises.

Using https://esm.sh/codemirror@6.0.1 and https://esm.sh/@codemirror/lang-javascript@6.1.7 as URLs just worked.

Looking at what is loaded, we see that the bundle contains contain multiple versions of the same module. However, the bundle is loaded correctly.

codemirror@6.0.1/codemirror.mjs
@codemirror/autocomplete@6.6.0/es2022/autocomplete.mjs
@codemirror/commands@6.2.3/es2022/commands.mjs
@codemirror/lang-javascript@6.1.7/es2022/lang-javascript.mjs
@codemirror/language@6.6.0/es2022/language.mjs
@codemirror/lint@6.2.1/es2022/lint.mjs
@codemirror/search@6.4.0/es2022/search.mjs
@codemirror/state@6.2.0/es2022/state.mjs
@codemirror/view@6.10.0/view.mjs
@codemirror/view@6.10.1/es2022/view.mjs
@lezer/common@1.0.2/es2022/common.mjs
@lezer/highlight@1.1.4/es2022/highlight.mjs
@lezer/javascript@1.4.3/es2022/javascript.mjs
@lezer/lr@1.3.4/es2022/lr.mjs
crelt@1.0.5/es2022/crelt.mjs
style-mod@4.0.3/es2022/style-mod.mjs
w3c-keyname@2.2.6/es2022/w3c-keyname.mjs

JSDelivr

Just using https://cdn.jsdelivr.net/npm/codemirror@6.0.1 and https://cdn.jsdelivr.net/npm/@codemirror/lang-javascript@6.1.7 loads both files as .cjs files.

The issue with this is that content-type is application/node, triggering the strict MIME type checking error.

This can be resolved by adding ?module to the URL which will load the files as ES Modules, with the correct MIME type.

At this point, the CodeMirror editor is loaded but syntax highlighting does not work.

But the "multiple instances" error is not triggered.

So what is going on?

When compare what is being loaded with what is loaded from esm.sh, we see that the bundle loads some extra files:

codemirror@6.0.1/dist/index.js
@codemirror/autocomplete@6.5.1/dist/index.js
@codemirror/autocomplete@6.6.1/dist/index.js
@codemirror/commands@6.2.2/dist/index.js
@codemirror/lang-javascript@6.1.7/dist/index.js
@codemirror/language@6.6.0/dist/index.js
@codemirror/lint@6.2.1/dist/index.js
@codemirror/search@6.3.0/dist/index.js
@codemirror/state@6.2.0/dist/index.js
@codemirror/view@6.10.1/dist/index.js
@codemirror/view@6.11.0/dist/index.js
@codemirror/view@6.9.2/dist/index.js
@codemirror/view@6.9.4/dist/index.js
@lezer/common@1.0.2/dist/index.js
@lezer/highlight@1.1.4/dist/index.js
@lezer/javascript@1.4.3/dist/index.es.js
@lezer/lr@1.3.4/dist/index.js
crelt@1.0.5/index.es.js
style-mod@4.0.3/src/style-mod.js
w3c-keyname@2.2.6/index.es.js

There are several things that could be tried to make the codemirror package work, but none of these work.

By loading all the packages directly, I managed to figure out that, with @codemirror/view pinned to the right version, the editor works.

Skypack

Just including https://cdn.skypack.dev/codemirror@v6.0.1 and https://cdn.skypack.dev/@codemirror/lang-javascript@v6.1.7 does not work. It triggers the "multiple instances" error, which isn't surprising if we look at everything that is loaded:

codemirror@v6.0.1
@codemirror/autocomplete@v6.0.2
@codemirror/autocomplete@v6.5.1
@codemirror/commands@v6.0.1
@codemirror/lang-javascript@v6.1.7
@codemirror/lang-javascript@v6.1.7
@codemirror/language@v6.0.0
@codemirror/language@v6.2.0
@codemirror/language@v6.6.0
@codemirror/lint@v6.0.0
@codemirror/search@v6.0.0
@codemirror/state@v6.0.0
@codemirror/state@v6.0.1
@codemirror/state@v6.1.0
@codemirror/state@v6.2.0
@codemirror/view@v6.0.0
@codemirror/view@v6.0.2
@codemirror/view@v6.8.1
@codemirror/view@v6.9.4
@codemirror/view@v6.9.5
@lezer/common@v1.0.0
@lezer/common@v1.0.2
@lezer/highlight@v1.0.0
@lezer/highlight@v1.1.3
@lezer/highlight@v1.1.4
@lezer/javascript@v1.4.2
@lezer/lr@v1.3.3
crelt@v1.0.5
style-mod@v4.0.0
style-mod@v4.0.2
style-mod@v4.0.3
w3c-keyname@v2.2.4
w3c-keyname@v2.2.6

As there don't seem to be other options available, this is as far as it goes.

UNPKG

Simply loading https://unpkg.com/codemirror@6.0.1 and https://unpkg.com/@codemirror/lang-javascript@6.1.7 does not work.

Not only are *.cjs files loaded but for some reason the content-type is set to text/plain.

Screenshot of UNPKG

Using the ?module query parameter loads the *.js files, but triggers the "multiple instances" error.

And by "multiple" it means "a lot":

codemirror@6.0.1/dist/index.js
@codemirror/autocomplete@%5E6.0.0
@codemirror/autocomplete@6.6.1
@codemirror/autocomplete@6.6.1/dist/index.js
@codemirror/commands@%5E6.0.0
@codemirror/commands@6.2.4
@codemirror/commands@6.2.4/dist/index.js
@codemirror/lang-javascript@6.1.7/dist/index.js
@codemirror/language@%5E6.0.0
@codemirror/language@%5E6.6.0
@codemirror/language@6.6.0
@codemirror/language@6.6.0
@codemirror/language@6.6.0/dist/index.js
@codemirror/language@6.6.0/dist/index.js
@codemirror/lint@%5E6.0.0
@codemirror/lint@6.2.1
@codemirror/lint@6.2.1/dist/index.js
@codemirror/search@%5E6.0.0
@codemirror/search@6.4.0
@codemirror/search@6.4.0/dist/index.js
@codemirror/state@%5E6.0.0
@codemirror/state@%5E6.1.4
@codemirror/state@%5E6.2.0
@codemirror/state@6.2.0
@codemirror/state@6.2.0
@codemirror/state@6.2.0
@codemirror/state@6.2.0/dist/index.js
@codemirror/state@6.2.0/dist/index.js
@codemirror/state@6.2.0/dist/index.js
@codemirror/view@%5E6.0.0
@codemirror/view@%5E6.6.0
@codemirror/view@6.11.1
@codemirror/view@6.11.1
@codemirror/view@6.11.1/dist/index.js
@codemirror/view@6.11.1/dist/index.js
@lezer/common@%5E1.0.0
@lezer/common@1.0.2
@lezer/common@1.0.2/dist/index.js
@lezer/highlight@%5E1.0.0
@lezer/highlight@%5E1.1.3
@lezer/highlight@1.1.4
@lezer/highlight@1.1.4
@lezer/highlight@1.1.4/dist/index.js
@lezer/highlight@1.1.4/dist/index.js
@lezer/javascript@%5E1.0.0
@lezer/javascript@1.4.3
@lezer/javascript@1.4.3/dist/index.es.js
@lezer/lr@%5E1.3.0
@lezer/lr@1.3.4
@lezer/lr@1.3.4/dist/index.js
crelt@%5E1.0.5
crelt@1.0.5
crelt@1.0.5/index.es.js
style-mod@%5E4.0.0
style-mod@4.0.3
style-mod@4.0.3/src/style-mod.js
w3c-keyname@%5E2.2.4
w3c-keyname@2.2.6
w3c-keyname@2.2.6/index.es.js

Trying to joad the *.cjs files combined with the ?module query parameter is not supported:

module mode is available only for JavaScript and HTML files