Example: Bundling with Rollup
CodeMirror is distributed as a collection of modules. These aren't
directly loadable by the browser—though modern browsers can load
EcmaScript modules, at the time of writing their mechanism for
resolving further dependencies is still too primitive to load
collections of NPM-distributed modules.
(That being said, there are solutions that work around that by
rewriting dependencies on the server side, like
Snowpack or
esmoduleserve. I
definitely recommend a solution like this during development, since it
tends to introduce less indirection and delay when changing files, but
for actual deployment, you'll want to do classical bundling for the
time being.)
Bundlers are tools that take a given main script (or in some cases
multiple scripts), and produce a new, generally bigger, script that has
all (or some) of the script's dependencies (and their dependencies,
and so on) included. This makes it easier to run modern JavaScript
systems, which tend to consist of a whole graph of dependencies, in
the browser. (But also, for example, to combine a library package
written as a group of files into a single file before uploading it to
NPM.)
As far as bundler software goes, I'm a big fan of
Rollup. But there are also other systems, like
Webpack and
Parcel, that work well and have their own
advantages.
To use Rollup to create a bundle that loads CodeMirror, you must first
write a main script (say, editor.mjs
) that imports the library and
creates the editor view.
import {EditorView, basicSetup} from "codemirror"
import {javascript} from "@codemirror/lang-javascript"
let editor = new EditorView({
extensions: [basicSetup, javascript()],
parent: document.body
})
Next, we must install the necessary packages. The
@rollup/plugin-node-resolve
package is necessary to teach rollup to resolve node-style
dependencies, so that it knows to find "@codemirror/lang-javascript"
under node_modules/@codemirror/lang-javascript/dist/index.js
.
# The CodeMirror packages used in our script
npm i codemirror @codemirror/lang-javascript
# Rollup and its plugin
npm i rollup @rollup/plugin-node-resolve
With these, we can run rollup to create the bundle file.
node_modules/.bin/rollup editor.mjs -f iife -o editor.bundle.js \
-p @rollup/plugin-node-resolve
The -f iife
file tells Rollup that the output file should be
formatted as an "immediately-invoked function expression" (as opposed
to other module styles, such as CommonJS or UMD). This means the code
will be wrapped in an anonymous function that is then immediately
called, using that function's scope as a local namespace so that its
variables don't end up in the global scope.
The -o
option indicates which output file to write to, and the -p
option loads the resolution plugin. You can also create a
configuration file (called rollup.config.mjs
) and just run rollup -c
to take the configuration from that file.
import {nodeResolve} from "@rollup/plugin-node-resolve"
export default {
input: "./editor.mjs",
output: {
file: "./editor.bundle.js",
format: "iife"
},
plugins: [nodeResolve()]
}
Now if you load your bundle with a script tag you'll see the editor in
your HTML page.
<!doctype html>
<meta charset=utf8>
<h1>CodeMirror!</h1>
<script src="editor.bundle.js"></script>
Bundle Size
Because the library is a hundred-thousand-line marvel of JavaScript
engineering, shipped with its full source code (including comments and
whitespace), bundles built in the most straightforward way can get
somewhat big (around 1 megabyte for the basic
setup and a language mode).
You can more than halve this by using something like
Terser or Babel to strip
the comments and whitespace, and rename variables to use shorter
names, getting the full bundle down to around 400 kilobytes (135
kilobytes when gzipped for transfer over the network).
The library is built in such a way that unused code can be eliminated
by a smart bundler like Rollup (a feature called “tree shaking”). The
most minimal editor (see below) avoids loading a bunch of extensions,
taking the full bundle size down to 700 kilobytes and reducing the
stripped code to 250 kilobytes (75 kilobytes gzipped).
import {EditorView, minimalSetup} from "codemirror"
let editor = new EditorView({
extensions: minimalSetup,
parent: document.body
})
When you need to support multiple languages, it can often be useful to
dynamically load the language support packages as needed to avoid the
amount of code the browser has to load. The Rollup
documentation can tell
you more about how to do this.