Radically Straightforward · Build
🏗️ Build static assets
Installation
$ npm install --save-dev @radically-straightforward/build
Usage
Author HTML, CSS, and browser JavaScript using tagged templates with @radically-straightforward/html, @radically-straightforward/css, and @radically-straightforward/javascript, for example:
source/index.mts
import fs from "node:fs/promises";
import childProcess from "node:child_process";
import server from "@radically-straightforward/server";
import html from "@radically-straightforward/html";
import css from "@radically-straightforward/css";
import javascript from "@radically-straightforward/javascript";
import * as caddy from "@radically-straightforward/caddy";
const application = server();
css`
@import "@radically-straightforward/javascript/static/index.css";
body {
background-color: blue;
}
`;
javascript`
/* Global JavaScript, including library initialization, global functions, and so forth. */
import * as javascript from "@radically-straightforward/javascript/static/index.mjs";
console.log(javascript);
`;
application.push({
method: "GET",
pathname: "/",
handler: (request, response) => {
response.end(html`
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="/${caddy.staticFiles["index.css"]}" />
<script src="/${caddy.staticFiles["index.mjs"]}"></script>
</head>
<body>
<h1
css="${css`
background-color: green;
`}"
javascript="${javascript`
console.log("Hello World");
`}"
>
@radically-straightforward/build
</h1>
</body>
</html>
`);
},
});
const caddyServer = childProcess.spawn(
"./node_modules/.bin/caddy",
["run", "--adapter", "caddyfile", "--config", "-"],
{ stdio: [undefined, "inherit", "inherit"] },
);
caddyServer.stdin.end(caddy.application());
Use @radically-straightforward/typescript and compile with TypeScript, which generates JavaScript files in the build/ directory.
Note: You may use other build processes, as long as they generate files at build/**/*.mjs.
Call @radically-straightforward/build:
Note: @radically-straightforward/build overwrites the files at build/**/*.mjs.
$ npx build
Parameters
--copy-with-hash: Globs of files to be copied into build/static/, for example, images, videos, and audios. The file names are appended with a hash of their contents to generate immutable names, for example, image.jpg may turn into image--JF98DJ2LL.jpg. Consult build/static.json for a mapping between the names (or use @radically-straightforward/caddy’s staticFiles, which reads from build/static.json). The --copy-with-hash parameter may be provided multiple times for multiple globs. By default the glob ./static/ is already included.
--copy-without-hash: The same as --copy-with-hash, but the names of the files are preserved. This is useful for favicon.ico and other files which must have particular names. By default the globs ./static/favicon.ico and ./static/apple-touch-icon.png are already included.
@radically-straightforward/build does the following:
-
Overwrites the files at build/**/*.mjs (and their corresponding source maps) to extract the tagged templates with CSS and browser JavaScript.
-
Uses esbuild to create a bundle of CSS and browser JavaScript. The result can be found in the build/static/ directory. The file names contain hashes of their contents to make them immutable, and you may consult build/static.json for a mapping between the names (or use @radically-straightforward/caddy’s staticFiles, which reads from build/static.json). The entrypoint of the CSS is at index.css and the entrypoint of the browser JavaScript is at index.mjs.
Note: The bundling process includes transpiling features such as CSS nesting, including CSS prefixes, minifying, and so forth.
Note: See @radically-straightforward/javascript for library support to use the extracted browser JavaScript.
-
Copies the files specified with --copy-with-hash and --copy-without-hash.
Interpolation
In CSS, interpolation is resolved at build time, which means that expressions must be self-contained and not refer to variables in scope.
For example, the following works:
css`
${["red", "green", "blue"].map(
(color) => css`
.text--${color} {
color: ${color};
}
`,
)}
`;
But the following does not work, because colors can’t be resolved at build time:
const colors = ["red", "green", "blue"];
css`
${colors.map(
(color) => css`
.text--${color} {
color: ${color};
}
`,
)}
`;
In some situations you may need to control the CSS at run time, depending on user data. There are two solutions for this, in order of preference:
-
Apply entire snippets of CSS conditionally, for example:
html`
<div
css="${userIsSignedIn
? css`
background-color: green;
`
: css`
background-color: red;
`}"
></div>
`;
-
When that isn’t viable either, use CSS variables in style="___", for example:
html`
<div
style="--background-color: ${userIsSignedIn ? "green" : "red"};"
css="${css`
background-color: var(--background-color);
`}"
></div>
`;
In browser JavaScript, interpolation is resolved at run time, and the values are transmitted from the server to the browser as JSON.
For example, the following works:
html`
<div
javascript="${javascript`
console.log(${["Hello", 2]});
`}"
></div>
`;
But the following does not work, because global browser JavaScript does not allow for interpolation:
javascript`
console.log(${["Hello", 2]});
`;
Related Work
These libraries only cover CSS, not browser JavaScript, some of them are tied to other libraries, for example, React, and some of them aren’t maintained anymore.