Building Velcro, part 1: internet-wide npm module resolution
Why a browser-based editor needed a module resolver that works in URLs instead of one filesystem.
- javascript
- modules
- browser tooling
Project / 2019 / earlier project
A browser-native module resolver and bundler that can load code from memory, a filesystem, or a CDN.
Velcro came out of a problem inside Plunker. The editor had to run projects whose files, package versions, and dependency graph only became known while the page was open. npm install and a node_modules tree did not make much sense there.
So I built a resolver that could fetch modules from memory, a filesystem, or a CDN. It assembles the graph on demand, then bundles or runs the result in the browser.
The entry file can exist only in memory while its bare npm dependency resolves from jsDelivr. A compound strategy makes both sources look like one module space.
const memory = new MemoryStrategy({
'/index.js': 'module.exports = require("react");',
'/package.json': JSON.stringify({
dependencies: { react: '^16.13.0' },
}),
});
const cdn = CdnStrategy.forJsDelivr(readUrl);
const modules = new CompoundStrategy({
strategies: [memory, cdn],
});
Velcro follows the imports across URL schemes, builds the graph, and can execute the bundle in the browser. The playground runs the idea for real.
Each dependency edge records the files and directories consulted during resolution. If a package.json changes, Velcro can invalidate the edges that depended on it instead of rebuilding the world. A transform can report its own input files in the same way.
That bookkeeping is what makes a browser-native development loop feel immediate even when the graph includes npm packages and generated code.