als-layout
als-layout
als-layout is a small, dependency‑light library for building and manipulating an HTML
document in memory — title, meta tags, Open Graph / Twitter cards, styles, scripts, links,
favicon and viewport — with a fluent, chainable API. It works the same in Node (SSR /
static generation) and in the browser.
It is built on top of als-document and is a
pure document builder: it has no global state and no site‑level concerns (collections,
sitemaps, robots). For multi‑page sites built on top of it, see
als-site.
What's new in 7.1
toDocument()— render a layout into the live DOM on the client (no‑op in Node).urlObj—url()now exposes the parsedURL.- Packaging — ESM resolves to the named
Layoutexport, CommonJS to the class (exportsmap). - Fix —
twitter:descriptionusesname=(wasproperty=), per the Twitter Cards spec. - Fix —
clonepreserves the document URL/host. - Change —
url()keeps the canonical trailing slash (URL.href) and storesurlObj.
See the full Changelog below.
Installation
npm i als-layout
ESM (named export)
import { Layout } from 'als-layout'
Node (CommonJS)
const Layout = require('als-layout')
Browser bundle (global Layout, als-document inlined)
<script src="layout.js"></script>
<script>const layout = new Layout()</script>
Quick start
import { Layout } from 'als-layout'
const layout = new Layout(undefined, 'https://example.com')
.lang('en')
.noindex() // adds <meta name="robots" content="noindex">
.viewport() // width=device-width, initial-scale=1.0
.title('Home') // <title> + og:title
.description('A cool site') // description + og/twitter description
.keywords(['stats', 'sheets'])
.favicon('/favicon.png')
.image('/cover.png') // og:image, twitter:image, twitter:card
.link('/styles.css') // <link rel=stylesheet> (deduped)
.style('body{margin:0}') // appends to <style>
.script({ src: '/app.js' }) // <script> in <head>
.script({}, 'console.log("hi")', false) // inline <script> in <body>
.url('/') // canonical + og:url, resolved vs host
layout.body.innerHTML = '<h1>Home</h1>'
console.log(layout.rawHtml) // full HTML string
A Layout is an offline DOM
This is the core idea: a Layout extends an als-document document, so the instance is a
real, in‑memory DOM tree you can query and mutate almost exactly like the browser DOM —
no jsdom, no browser, works in plain Node. The chainable title()/meta()/link()/… helpers
are just convenience on top of it; for anything else you manipulate nodes directly.
const layout = new Layout(/*html*/`<html><head></head><body><main></main></body></html>`)
// Query like the DOM ($ / $ are aliases for querySelector / querySelectorAll)
const main = layout.$('main') // === layout.querySelector('main')
const items = layout.$('.item') // === querySelectorAll, returns an array
// Build and insert nodes
main.innerHTML = '<h1>Hello</h1>'
main.insert(2, '<p class="lead">Intro</p>') // append parsed HTML
main.insertAdjacentHTML('beforeend', '<hr>')
// Mutate elements with the familiar API
const h1 = layout.$('h1')
h1.setAttribute('id', 'top')
h1.classList.add('title')
h1.dataset.role = 'heading'
h1.textContent = 'Welcome'
console.log(h1.outerHTML) // <h1 id="top" class="title" data-role="heading">Welcome</h1>
// Traverse
h1.parent // parent node
h1.nextElementSibling
layout.body.children
// Remove
layout.$('hr').remove()
Supported on nodes: querySelector/$, querySelectorAll/$, getElementById,
getAttribute/setAttribute/removeAttribute, classList, dataset, style,
innerHTML/outerHTML/textContent, append/insert/insertAdjacentHTML/remove,
plus traversal (parent, children, prev/next, …). See
als-document for the full node API.
On the client, toDocument() flushes this offline DOM into the live document.
Cloning
layout.clone returns a deep, independent copy — ideal as a template for many pages.
Changes to a clone never affect the original.
const base = new Layout(template, 'https://example.com').link('/styles.css')
const about = base.clone.title('About').url('/about')
API
Constructor
new Layout(html?: string, host?: string)
- html — optional HTML string to start from.
- host — base URL used to resolve relative URLs in
url().
Properties
rawHtml— current document as an HTML string.clone— a deep, independent copy.urlObj— the parsedURLof the lasturl()call (set afterurl()).head/body/html— element accessors (fromals-document).
Methods (all chainable, return this)
lang(lang)— sets<html lang>.title(title)— sets<title>andog:title.description(text)— setsdescription,og:description,twitter:description.keywords(list)— sets/mergesmeta[name=keywords].meta(props)— sets/updates a<meta>tag (keyed by its first attribute).image(src)— setsog:image,twitter:image,twitter:card.favicon(href)— sets/updates the favicon link.viewport(content?)— sets the viewport meta.link(href, attributes?)— adds a stylesheet<link>(skipped if already present).style(css)— appends CSS to a<style>element.script(attrs?, innerHTML?, head = true)— adds a<script>(head or body;srcdeduped).url(url, host = this.URL)— sets canonical +og:url, storesurlObj.toDocument()— in the browser, renders this layout into the livedocument(no‑op in Node); returnsthis.
Changelog
7.1.0
- Added
toDocument()— render a layout into the live DOM on the client. - Added
urlObj—url()now exposes the parsedURL. - Added
exportsmap — ESM resolves to the namedLayoutexport, CommonJS to the class. - Fixed
twitter:descriptionnow usesname=(wasproperty=), per the Twitter Cards spec. - Fixed
clonepreserves the document URL/host. - Changed
url()keeps the canonical trailing slash (URL.href), and storesurlObj.
7.0.0 (breaking)
- Merged into a single source file; added ESM build (
index.mjs) and browser bundle (layout.js). - Removed
status,end,minified,propsToClone, and JS/CSS uglification.
Notes
- Pure document builder — no global state, no file I/O.
index.mjsand the browserlayout.jsare generated fromsrc/layout.jsbynode build.js.- Use
cloneto derive page variants from a base template.