Been doing some HTMX lately using ExpressJS backend.
Unfortunately, my CSS files are not automatically minified and bundled for the browser.
Using lightningcss, a blazingly fast tool written in rust, we can achieve some minifications and combining using some NodeJS scripts.
npm i -D lightningcss lightningcss-cli
Note: You may not need the cli package but it can be useful when testing broken css syntax.
Source files: public/assets
Destination files: dist/public/assets
I could have a nice JSON config file but I just write the config on the script instead.
The script contains a list of CSS files and will minify and combine them in groups and save into the dist/public/assets
dir.
Note: CSS may reference resources using relative path so it is tricky to bundle them together.
You may bundle together related CSS files or switch to absolute path.
File: scripts/bundle-css.js
const fs = require('fs/promises');
const lightningcss = require('lightningcss');
const path = require('node:path');
const ROOT_DIR = path.join(__dirname, '..');
const SOURCE_DIR = path.join(ROOT_DIR, 'public');
const DEST_DIR = path.join(ROOT_DIR, 'dist', 'public');
// Organize css files in bundles
const bundles = [
{
bundle: 'assets/vendors/vendor1/vendor1-bundle.css',
files: [
'assets/vendors/vendor1/css/file1.css',
'assets/vendors/vendor1/css/file2.css',
'assets/vendors/vendor1/css/file3.css',
]
},
{
bundle: 'assets/css/main-bundle.css',
files: [
'assets/css/style.css',
'assets/css/tables.css',
'assets/css/custom.css',
]
},
{
bundle: 'assets/css/other-bundle.css',
files: [
'assets/css/other1.css',
'assets/css/other2.css',
'assets/css/other3.css',
]
},
];
async function minifyBundle(destFile, files) {
// Compile all file contents
let contents = '';
for (const file of files) {
const content = await fs.readFile(file);
contents = contents.concat(content.toString(), "\n");
}
const pathChunks = destFile.split('/');
const filename = pathChunks.pop();
let { code, map } = lightningcss.transform({
filename: filename,
code: Buffer.from(contents),
minify: true,
sourceMap: true
});
// Save code and map
const destMap = `${destFile}.map`;
await fs.writeFile(destFile, code);
await fs.writeFile(destMap, map);
}
async function run() {
for (const bundle of bundles) {
const destPath = path.join(DEST_DIR, ...bundle.bundle.split('/'));
const files = bundle.files.map(file => {
return path.join(SOURCE_DIR, ...file.split('/'));
});
await minifyBundle(destPath, files);
}
}
run();
node scripts/bundle-css.js
The script will create the following files in the output directory.
dist/public/assets/vendors/vendor1/vendor1-bundle.css
dist/public/assets/css/main-bundle.css
dist/public/assets/css/other-bundle.css
These files can be referenced in the template like below:
<link rel="stylesheet" href="/assets/vendors/vendor1/vendor1-bundle.css" />
<link rel="stylesheet" href="/assets/css/main-bundle.css" />
<link rel="stylesheet" href="/assets/css/other-bundle.css" />
That's it!