ucdn

A µcompress based CDN utility, compatible with both Express and native http module

Usage no npm install needed!

<script type="module">
  import ucdn from 'https://cdn.skypack.dev/ucdn';
</script>

README

µcdn

Build Status Coverage Status

delivering packages

Social Media Photo by Eduardo Casajús Gorostiaga on Unsplash

A ucompress based utility that accepts a configuration object with a source path, an optional dest, which fallbacks to the temp folder, plus eventually extra headers property to pollute headers via allow-origin among other details.

📣 Community Announcement

Please ask questions in the dedicated forum to help the community around this project grow ♥


Example

The following example will serve every file within any folder in the source directory, automatically optimizing on demand all operations, including the creation of brotli, gzip, or deflate.

import {createServer} from 'http';
import {join} from 'path';

import umeta from 'umeta';
const {dirName} = umeta(import.meta);

import ucdn from 'ucdn';
const callback = cdn({
  cacheTimeout: 1000 * 60, // 1 min cache
  source: join(dirName, 'source'),
  // dest: join(dirName, 'dest')
});

createServer(callback).listen(8080);

The callback works with Express too, and similar modules, where all non existent files in the source folder will be ignored, and anything else will execute regularly.

const {join} = require('path');

const express = require('express');
const ucdn = require('ucdn');

const app = express();
app.use(ucdn({
  source: join(__dirname, 'source'),
  dest: join(__dirname, 'dest')
}));
app.get('/unknown', (req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('OK');
});
app.listen(8080);

As binary file

It is possible to bootstrap a micro CDN right away via npx ucdn. Pass --help to see options.

The --verbose output

If started via --verbose flag, each request will be logged producing the following output example:

200 XXms /full/path.html.gzip

404 /full/nope.html

200 /favicon.ico

404 /full/nope.html

304 /full/path.html.gzip

500 /full/error-during-compression

Please note that 200 is the file status in the cdn, not the response status for the browser. The status indeed indicates that the file wasn't known/compressed yet, and it took Xms to be generated.

On the other hand, whenever a file was already known, it will be served like a 304 as the cdn didn't need to perform any operation.

Basically, the status reflects the cdn and not whenever a browser is requesting new content or not.

About API

If ucdn is started with an --api ./path flag, files in that folder will be used as fallback.

The API folder does not need to be reachable, or included, within static assets (source).

// @file ./api/hello.js
// @start ucdn --api ./api --source ./public

const headers = {'content-type': 'text/html;charset=utf-8'};

// export a function that will receive
// the request and response from the server
module.exports = (req, res) => {
  res.writeHead(200, headers);
  res.end('<h1>Hello API!</h1>');
};

Please note that currently, and for the time being, files in API folder must be CommonJS compatible, and with a .js extension.

If your project uses ESM instead, remember to put {"type":"commonjs"} inside the ./api/package.json file.

Performance

Differently from other solutions, the compression is done once, and once only, per each required static asset, reducing both RAM and CPU overhead in the long run, but being a bit slower than express static, with or without compressed outcome, in the very first time a file, that hasn't been optimized yet, is requested.

However, once each file cache is ready, µcdn is 1.2x, up to 2.5x, faster than express with static and compress, and it performs specially well in IoT devices that are capable of running NodeJS.

About cacheTimeout

The purpose of this module is to do the least amount of disk operations, including lightweight operations such as fs.stat(...) or fs.readFile(...). There are also heavy operations such the runtime compression, which should be guarded against concurrent requests.

In order to do so, µcdn uses an internal cache mechanism that avoid checking stats, parsing JSON, or compressing missing or updated assets during this timeout, which is by default 1000 milliseconds.

If you pass a timeout with value 0, it will never check ever again anything, and all JSON headers and stats results will be kept in RAM until the end of the program, unless some file is missing, or some error occurs.

In every other case, using a minute, up to 10 minutes, as cache timeout, is rather suggested.

Compatibility

Beside own dependencies that might have different compatibility requirements, this module works in NodeJS 10 or higher.