Got annoyed by weird indentation issues with multiline strings, so I made `@okikioOkiki Ojo/undent`, a tiny dedent utility for template literals.

Strips leading spaces from multiline strings fixing the formatting...it's designed to be versatile and flexible.

`@okikioOkiki Ojo/undent` preserves newlines, handles interpolations, and avoids the usual formatting bugs. Zero dependencies + works in Node, Deno, and Bun.

github: github.com/okikio/undent
npm: npmjs.com/@okikio/undent
jsr: jsr.io/@okikio/undent

/1

import { align, undent } from "@okikio/undent";
// + = space (shown explicitly to make indentation visible)
// align() — multi-line values stay at their insertion column
| const items = "- alpha\n- beta\n- gamma";

// without align()
console.log(undent"

list:

S{items}

end
ND)
// list:
// --- alpha
// - beta « snaps to column ©
// - gamma
// end
// with align()
console.log(undent"

list:

${align(items)}

end
ND)
// list:
// --- alpha
// --- beta « stays at insertion column
// --- gamma
// end
import { embed, undent } from "@okikio/undent";
// + = space (shown explicitly to make indentation visible)
// embed() — strip a value's own indent, then align it
| const sql = °
SELECT id, name
FROM users
WHERE active = true
// without embed()
console.log(undent"
query:
${sql}
Df
// query:
// ----SELECT-id, ‘name « baked-in indent bleeds through
// ----FROM-- -users
// ----WHERE--active-=-true
//
// with embed()
console.log(undent"
query:
${embed(sql)}
Df
// query:
// --SELECT-id, -name
// --FROM-- -users
// --WHERE--active-=-true
import { indent, undent } from "@okikio/undent";
// + = space (shown explicitly to make indentation visible)
// indent anchor — explicit column-0 baseline
| // without anchor — content deeper than min indent keeps its relative offset
console.log(undent"
if (ready) {
run();
}
ND)
// if: (ready) -{
// --run();
/1}
// with anchor — anchor column becomes column 0; content deeper than anchor keeps offset
console.log(undent"
${indent}
if (ready) {
run();
}
ND)
// --if- (ready) -{ « 2 cols deeper than anchor, preserved
// ----run();
l/l}
import { align, undent } from "@okikio/undent";

// + = space (shown explicitly to make indentation visible)

// newline support — \r\n and \r pass through untouched

| const crlf = "A\r\nB\r\nC";

// with undent + align — CRLF in values is never touched

console.log(JSON.stringify(undent prefix\n${align(crlf)} ));

// "prefix\nA\r\nB\r\nC"

// 6. Trim modes — per-side, fine-grained control

// default: "all" - strips all blank wrapper lines

console.log(JSON.stringify(undent"’
hello

9)

// "hello"

// "none" - keeps blank lines at both ends

console.log(JSON.stringify(undent.with({ trim: "none" })°
hello

9)

// "\nhello\n"

// "one" — strips at most one blank line from each end

console.log(JSON.stringify(undent.with({ trim: "one" })°
hello

9)

// "hello"

// per-side: keep leading blank, strip trailing

console.log(JSON.stringify(undent.with({ trim: { leading: "none", trailing: "all" } })°
hello

9)

// "\nhello"
0

If you have a fediverse account, you can quote this note from your own instance. Search https://mastodon.social/users/okikio/statuses/116107922637513556 on your instance and quote it. (Note that quoting is not supported in Mastodon.)