See it live before reading!
Vanilla JS is the best! Except when it's not.
Even this tiny app of ours is a tad bloated and makes a scandalous number of network connections on first load. Part of that due to us loading entire JS modules when we only need pieces of them. Truth be told, another part is due to me not realizing that including md
in the list of languages I wanted Shiki (the syntax highlighter) to support included like every language definition b/c ofc it did.
Anywayβ¦
In this experiment, we're going to rollup
our sleeves and tighten things up a bit. We're still staying in unadulterated Vanilla JS land, but we're going to lean on some tooling from the Node.js ecosystem to help our project go on a diet when we deploy it to a real environment.
Before we do that, I feel compelled to go over a couple changes I've made to the previous application to help support this weight reduction program. Here's the updated directory structure with noted about what'd been added or changed; I'll only be covering said changes, so see the previous entries for what everything is for:
rollup-vanilla-webr
βββ README.md
βββ css
β βββ style.css
βββ dist # <--------------------------- This is new!
β βββ index.browser.mjs
β βββ onig.wasm
βββ favicon.ico
βββ img
β βββ preview.png
βββ index.html # <--------------------- This has been modified.
βββ justfile # <----------------------- This has been modified.
βββ languages # <---------------------- This is new!
β βββ css.tmLanguage.json
β βββ html.tmLanguage.json
β βββ java.tmLanguage.json
β βββ javascript.tmLanguage.json
β βββ json.tmLanguage.json
β βββ markdown.tmLanguage.json
β βββ nginx.tmLanguage.json
β βββ python.tmLanguage.json
β βββ r.tmLanguage.json
β βββ shellscript.tmLanguage.json
β βββ typescript.tmLanguage.json
β βββ xml.tmLanguage.json
βββ main.js # <------------------------ This has been modified.
βββ md
β βββ main.md
βββ package.json # <------------------- This is new!
βββ r.js
βββ renderers.js # <------------------- This has been modified.
βββ rollup.config.js # <--------------- This is new!
βββ themes
β βββ ayu-dark.json
βββ utils.js
βββ wc
β βββ region-plot.js
β βββ select-list.js
β βββ status-message.js
βββ webr-serviceworker.js.map
βββ webr-worker.js.map
I finally figured out how to get Shiki loaded as a local ES6 module, which is why there's a dist/
folder. I did this for many reasons, but one big one was to get it out of index.html
so we could ship it from wherever we deploy it vs hit the CDN. Shiki is hard coded to use dist/
and I didn't feel like re-bundling it to be more flexible (I will read up to see if it's configurable when called). The languages/
and themes/
folders go with it, and since I figured out why it was loading way too many language support files, I pared those down quite a bit to just what we're using.
We got rid of the Web Components in index.html
and now load them as modules in main.js
via:
import "./wc/region-plot.js"
import "./wc/select-list.js"
import "./wc/status-message.js"
This enables them to be "rolled up" when we're ready to do so.
In renderers.js
we now import
Shiki:
import * as shiki from './dist/index.browser.mjs';
Overall, this was just minor surgery, and we'll cover package.json
in the next section.
To use Rollup we need a JS runtime environment and Node is as good as any since we're just using it to run rollup
. So, please install npm before continuing.
Now, we'll install npx
, which is just an easier way to run npm JS scripts, then rollup
, globally to have it around for the future, and then install it local to the project along with some rollup helpers we'll be using.
npm install -g npx
npm install rollup --global
npm install
You now have a node_modules
directory in the project directory. It's HUGE (yes, I am going to make you scroll past ~140 entries):
node_modules
βββ @ampproject
βββ @babel
βββ @gar
βββ @jridgewell
βββ @nodelib
βββ @npmcli
βββ @tootallnate
βββ @types
βββ @web
βββ acorn
βββ agent-base
βββ agentkeepalive
βββ aggregate-error
βββ ansi-styles
βββ array-union
βββ balanced-match
βββ brace-expansion
βββ braces
βββ browserslist
βββ buffer-from
βββ cacache
βββ camel-case
βββ caniuse-lite
βββ chalk
βββ chownr
βββ clean-css
βββ clean-stack
βββ color-convert
βββ color-name
βββ colorette
βββ commander
βββ concat-map
βββ convert-source-map
βββ data-uri-to-buffer
βββ debug
βββ depd
βββ dir-glob
βββ dot-case
βββ electron-to-chromium
βββ encoding
βββ err-code
βββ escalade
βββ escape-string-regexp
βββ fast-glob
βββ fastq
βββ fill-range
βββ fs-extra
βββ fs-minipass
βββ fs.realpath
βββ fsevents
βββ gensync
βββ glob
βββ glob-parent
βββ globals
βββ globby
βββ graceful-fs
βββ has-flag
βββ he
βββ html-minifier-terser
βββ http-cache-semantics
βββ http-proxy-agent
βββ https-proxy-agent
βββ humanize-ms
βββ iconv-lite
βββ ignore
βββ imurmurhash
βββ indent-string
βββ infer-owner
βββ inflight
βββ inherits
βββ ip
βββ is-extglob
βββ is-glob
βββ is-lambda
βββ is-number
βββ is-plain-object
βββ js-tokens
βββ jsesc
βββ json5
βββ jsonfile
βββ lower-case
βββ lru-cache
βββ make-fetch-happen
βββ merge2
βββ micromatch
βββ mime-db
βββ mime-types
βββ minimatch
βββ minipass
βββ minipass-collect
βββ minipass-fetch
βββ minipass-flush
βββ minipass-pipeline
βββ minipass-sized
βββ minizlib
βββ mkdirp
βββ ms
βββ negotiator
βββ no-case
βββ node-releases
βββ once
βββ p-map
βββ param-case
βββ parse5
βββ pascal-case
βββ path-is-absolute
βββ path-type
βββ picocolors
βββ picomatch
βββ promise-inflight
βββ promise-retry
βββ queue-microtask
βββ relateurl
βββ retry
βββ reusify
βββ rimraf
βββ rollup
βββ rollup-plugin-copy
βββ rollup-plugin-url-resolve
βββ run-parallel
βββ safer-buffer
βββ semver
βββ slash
βββ smart-buffer
βββ socks
βββ socks-proxy-agent
βββ source-map
βββ source-map-support
βββ ssri
βββ supports-color
βββ tar
βββ terser
βββ to-fast-properties
βββ to-regex-range
βββ tslib
βββ unique-filename
βββ unique-slug
βββ universalify
βββ update-browserslist-db
βββ wrappy
βββ yallist
May I never complain about the {tidyverse} dependency Hades ever again.
We're now ready to roll things up.
The last "new" file is rollup.config.js
. Think of this like a "justfile" or "Makefile" with some extra bits tacked on. It's just instructions for how we want to get our project put into a better format for serving in production:
// this is what were using from what we put into `package.json`
import urlResolve from 'rollup-plugin-url-resolve';
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
import copy from 'rollup-plugin-copy';
export default [
{
input: './main.js', // rollup will inspect this
// and the entire tree of imports it relies on
output: {
dir: 'build', // We're putting all the output files/dirs here
format: 'es' // And we still want ES6 modules
},
plugins: [
urlResolve({ // ππΌ see below the code
cacheManager: '.cache',
minify: true,
}),
html({ // ππΌ see below the code
input: 'index.html',
minify: true,
}),
copy({ // ππΌ see below the code
targets: [
{ src: 'dist/onig.wasm', dest: 'build/dist' },
{ src: 'md/**/*', dest: 'build/md' },
{ src: 'languages/**/*', dest: 'build/languages' },
{ src: 'themes/**/*', dest: 'build/themes' },
{ src: 'img/**/*', dest: 'build/img' },
{ src: '*.map', dest: 'build' },
{ src: 'favicon.ico', dest: 'build' },
]
})
]
}
];
Plain ol' rollup
will just care about the JS dependencies. If we have extra bits we need to put into the build
directory, we have to tell it to do that. One way is to use that "copy
" plugin and specify stuff by hand. For small projects like these, that's 100% fine.
The html
plugin will also figure out things to add from our index.html
(like the CSS file). It would have handled the <script>
tags, too, but it would have kept us relying on a CDN for Shiki. Blech.
urlResolve
lets me me lazy and still rely on CDNs during development. It'll fetch and .cache
those resources so they can be further scrunched, shaken and come along for the ride from our server.
The justfile
has been changed to give us a "rollup" job:
rollup:
rm -rf build/
npx rollup --config # use the default config file
After a just rollup
we have a new build/
directory!
build
βββ assets
β βββ style-5c0658bc.css
βββ dist
β βββ onig.wasm
βββ favicon.ico
βββ img
β βββ preview.png
βββ index.html
βββ languages
β βββ css.tmLanguage.json
β βββ html.tmLanguage.json
β βββ java.tmLanguage.json
β βββ javascript.tmLanguage.json
β βββ json.tmLanguage.json
β βββ markdown.tmLanguage.json
β βββ nginx.tmLanguage.json
β βββ python.tmLanguage.json
β βββ r.tmLanguage.json
β βββ shellscript.tmLanguage.json
β βββ typescript.tmLanguage.json
β βββ xml.tmLanguage.json
βββ main.js
βββ md
β βββ main.md
βββ themes
β βββ ayu-dark.json
βββ webr-serviceworker.js.map
βββ webr-worker.js.map
You should poke at main.js
and index.html
to see how mangled they are.
The rsync
just
job is now rsync -avp ./build/ rud.is:~/rud.is/w/rollup-vanilla-webr/
: it is literally how i deployed what you're seeing.
https://rud.is/w/lit-webr-plot/
makes over 80 HTTP requests, with most hitting the jsdelivr CDN. The Network tab of DevTools scrolls too much to see it.
Here's what our reduced version does (just over 20):
We are by no means finished with optimizing things, but this "rollup" thing can be a bit intimidating for folks who aren't JS natives.
Hit up GH: https://github.com/hrbrmstr/rollup-vanilla-webr for the source and drop any issues if anything needs more explanation.
Brought to you by @hrbrmstr