hrbrmstr / rollup-vanilla-webr

πŸ§ͺ Vanilla JS WebR + Rollup For Smaller & Easier Deploys

Home Page:https://rud.is/w/rollup-vanilla-webr/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

πŸ§ͺ Vanilla JS WebR + Rollup For Smaller & Easier Deploys

See it live before reading!

Yo Yo Yo!

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.

Giving In To Node/npm/npx

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.

Rollup Time!

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.

Proving It's Better

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):

FIN

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

About

πŸ§ͺ Vanilla JS WebR + Rollup For Smaller & Easier Deploys

https://rud.is/w/rollup-vanilla-webr/


Languages

Language:JavaScript 70.3%Language:CSS 14.0%Language:HTML 9.7%Language:Just 6.1%