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.
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 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:
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.
For CDN versions of CodeMirror to work, some things have to go right.
- The CDN has to be able to bundle/compile NPM modules.
- The CND has to serve the module with the correct MIME type.
- 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.
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.
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).
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:
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
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/view@6.9.2/dist/index.js
@codemirror/view@6.9.4/dist/index.js
@codemirror/autocomplete@6.5.1/dist/index.js
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.
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.
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
.
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
I'm getting it to more-or-less work with UNPKG + importmap.
EDIT: CAVEATS:
I suppose the Right Way to use large importmaps is generate them from npm/yarn resolving versions? But that's getting not fun, all I wanted is to avoid bundlers + package managers...
Without the importmap, it needed the
?module
so that cross-package imports resolve, but apparently there were duplicates, as the browser didn't realizehttps://unpkg.com/@codemirror/state
that my code is importing was same as@codemirror/state
that other CodeMirror modules are importing (and at least once i saw a CodeMirror console error suggestingstate
must be imported only once)...With importmap, I can drop
?module
conversion, but need sup-paths to get ESM sources — as default path unpkg picks tends to give CJS versions. The sub-paths are nearly uniformdist/index.js
(except for lezer/javascript).