buildy bundly buddies for websites and web apps
npm install --save-dev @e280/scute@e280/scute contains three lil buddies:
- ๐ข #scute โ eponymous zero-config build cli
- ๐ช #ssg โ html templating library for static site generation
- ๐ #octo โ watch routine terminal multiplexer cli
"the golden path."
just an example of how we like to setup our projects
- we setup our typescript apps with ts code in
s/dir, and outputting tox/dir - we install a server like
http-serverfor previewing our app in development - we setup a
tests.test.tstest suite with @e280/science - we add these scripts to package.json
"scripts": { "build": "rm -rf x && tsc && scute -v", "test": "node x/tests.test.js", "watch": "npm run build && octo 'scute -vw' 'tsc -w' 'node --watch x/tests.test.js' 'http-server x'" }
- now we can run these commands
npm run buildโ run your project buildnpm run watchโ start a watch routinenpm run testโ run our test suite
zero-config build tool and static site generator
scutecommand builds your projectscute -vfor verbose mode so you can see what's upscute -vwfor watch modescute --help๐ข scute {params} readme https://github.com/e280/scute zero-config static site generator - bundles .bundle.js files with esbuild - copies files like .css and .json - builds .html.js template files - executes .exe.js scripts --watch, -w, flag boolean watch mode --in, default string-list s dirs to read from --out, default string x output dir --bundle, default boolean yes should we bundle .bundle.js files? --copy, default string-list **/*.css,**/*.json,**/*.txt what files should we copy verbatim? --html, default boolean yes should we build .html.js templates? --exe, default boolean yes should we execute .exe.js scripts? --debounce, default number 200 milliseconds to wait before watch routine build --exclude, optional string-list what files should we ignore? --verbose, -v, flag boolean should we log a bunch of crap?- by default we use
s/andx/, instead ofsrc/anddist/. it's our weird e280 tradition. if you wanna be a normie, do this:scute --in="src" --out="dist"
library for static site generation
import {ssg, html} from "@e280/scute"ssgis a toolkit for templating and paths and stuffhtmlis for writing html, and interleaving it with async jsorbdoes path/url magic and hash-version cache-busting
- make a web page. it makes an document. let's make
index.html.tsexport default ssg.page(import.meta.url, async orb => ({ title: "cool website", js: "main.bundle.min.js", css: "main.css", dark: true, favicon: "/assets/favicon.png", head: html` <meta name="example" value="whatever"/> `, socialCard: { themeColor: "#8FCC8F", title: "scute", description: "buildy bundly buddies", siteName: "https://e280.org/", image: `https://e280.org/assets/e.png`, }, body: html` <h1>incredi website</h1> `, }))
did you notice the
orb? we must'nt yet speak of the all-powerful orb.. ssg.pageis just sugar which produces an ordinaryTemplateFn
- html templating fns, with protection against injection attacks
- basic usage
html`<div>hello</div>`
- it returns an
Htmlinstanceconst h = html`<div>hello</div>` h.toString() // "<div>hello</div>"
- strings are safely sanitized
html`<div>${"<evil/>"}</div>`.toString() // "<div><evil/></div>"
html.rawto circumvent sanitization (๐จ allowing injection attacks, lol)html`<div>${html.raw("<evil/>")}</div>`.toString() // "<div><evil/></div>"
html.renderproduces a string. it's async and resolves injected promisesthe rendering is handled automatically by the scute cliawait html`<div>${Promise.resolve("async magic")}</div>`.render() // "<div>async magic</div>"
ssg.templateproduces aTemplateFn.
if it's the default export, and your module has a.html.tsor.html.jsextension, the scute build cli will automatically build the.htmlpage.page.html.tsimport {partial} from "./partial.js" export default ssg.template(import.meta.url, async orb => html` <!doctype html> <html> <head> <title>scute</title> <script type="module" src="${orb.hashurl("main.bundle.min.js")}"></script> </head> <body> <h1>scute is good</h1> ${orb.place(partial)} </body> </html> `)
orb.placeto insert one template into another, while maintaining relative pathing..
- we can inject one ssg template into another. let's make
partial.tsexport const partial = ssg.template(import.meta.url, async orb => html` <div> <img alt="" src="${orb.url("../images/turtle.avif")}"/> </div> `)
- orb.url is relative to our module,
partial.ts
- orb.url is relative to our module,
relative paths and urls
- the orb's superpower is dealing with paths and urls
- the orb allows you to reference files relative to the current template module,
regardless of how you import html partials from all around your codebase.
this should impress you.
orb.url("main.css")โ ๐งโโ๏ธ important! for clientside browser urls!
this outputs a browser url relative to the page (not partial).
don't get confused here! the input is relative following the magic conventions. the output is page-relative.
eg, you can use these urls as<script>srcand such.orb.path("main.css")โ ๐งโโ๏ธ important! for serverside filesystem paths!
this outputs a filesystem path relative to the current working directory.
eg, you can use these paths in nodereadFilecalls and such.orb.hashurl("main.css")โ ๐งโโ๏ธ important! saves you from browser cache problems!
likeorb.url, but it attaches a hash-version query param to the url.
looks likemain.css?v=cdd9738a8eda.
this allows the browser to properly cache that exact version of the content.
anything usingorb.hashurlwill not have stale caching problems in your deployments.
yes, it's reading the target file on disk and producing a sha256 hash of it.
- ๐งโโ๏ธ important! all orb functions that expect path strings respect these conventions
"main.css"โ relative to the current template module"./main.css"โ relative to the current template module (same as above)"/main.css"โ relative to the server root (aka, your scute --out dir, maybex/)"$/main.css"โ relative to the build process current working directory
orb.place(partial)
prepare a partial template for insertion into this template, preserving relative pathing magic.orb.inject("main.css")
read the contents of that file, and inject it raw without sanitization.
used to insert text directly, like <style>, <script>, json, stuff like that.orb.packageVersion()
returns theversionstring found in yourpackage.json.
orb.io.read("main.css")โ read a text fileorb.io.write("main.css", "* {}")โ write a text fileorb.io.readJson("$/package.json")โ read and parse a json fileorb.io.writeJson("$/package.json", {})โ write json to a file
- your
.exe.tsmodules will be automatically executed, and they must provide a default exported exe fn like this:export default ssg.exe(import.meta.url, async orb => { await orb.io.write("blog.txt", "lol") })
- this gives you access to an orb, which is useful for resolving paths relative to this module.
- eg, imagine a script like
blog.exe.tswhere you read hundreds of markdown files and emit a webpage for each, or something like that.
tiny watch routine terminal multiplexer
octo 'scute -vw' 'tsc -w'command runs your watch routine- each subcommand gets its own pane
- press
[and]to shimmy between panes, q to quit octo --help๐ octo ...commands tiny terminal multiplexer for watch routines ...commands, each command gets its own pane that you can flip between. for example, $ octo "scute -vw" "tsc -w" this will give you two panes, - press 1 to see the scute output - press 2 to see the tsc output - press [ or h or j to shimmy left - press ] or l or k to shimmy right - press backspace to clear the pane - press q or ctrl+c to quit local npm bin is available, $ scute -vw # GOOD this works $ npx scute -vw # BAD npx is unnecessary- here's a typical 4-pane watch routine with octo
octo \ "scute --verbose --watch" \ "tsc -w" \ "node --watch x/tests.test.ts" \ "http-server x"
reward us with github stars
build with us at https://e280.org/ but only if you're cool
