Skip to content
Prev Previous commit
Next Next commit
Rework standalone CLI build setup
This is equivalent to the old build setup but doesn’t need to use CLI commands to build the binaries
  • Loading branch information
thecrypticace committed Nov 30, 2025
commit 65e721d9968c3fa30cdea4c3bca8e13d0d2df1ed
115 changes: 48 additions & 67 deletions packages/@tailwindcss-standalone/scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,66 @@
import { $ } from 'bun'
import { createHash } from 'node:crypto'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = fileURLToPath(new URL('.', import.meta.url))

async function buildForPlatform(triple: string, outfile: string) {
// We wrap this in a retry because occasionally the atomic rename fails for some reason
for (let i = 0; i < 5; ++i) {
try {
let cmd = $`bun build --compile --target=${triple} ./src/index.ts --outfile=${outfile} --env inline`

// This env var is used by our patched versions of Lightning CSS and Parcel Watcher to
// statically bundle the proper binaries for musl vs glibc
cmd = cmd.env({
PLATFORM_LIBC: triple.includes('-musl') ? 'musl' : 'glibc',

// Workaround for Bun binary downloads failing on Windows CI when
// USERPROFILE is passed through by Turborepo.
USERPROFILE: '',
})

return await cmd
} catch (err) {
if (i < 5) continue

throw new Error(`Failed to build for platform ${triple}`, { cause: err })
}
}
}

async function build(triple: string, file: string) {
let start = process.hrtime.bigint()

let outfile = path.resolve(__dirname, `../dist/${file}`)

await buildForPlatform(triple, outfile)

await new Promise((resolve) => setTimeout(resolve, 100))
// Workaround for Bun binary downloads failing on Windows CI when
// USERPROFILE is passed through by Turborepo.
process.env.USERPROFILE = ''

// We use baseline builds for all x64 platforms to ensure compatibility with
// older hardware.
let builds: { target: Bun.Build.Target; name: string }[] = [
{ name: 'tailwindcss-linux-arm64', target: 'bun-linux-arm64' },
{ name: 'tailwindcss-linux-arm64-musl', target: 'bun-linux-arm64-musl' },
{ name: 'tailwindcss-linux-x64', target: 'bun-linux-x64-baseline-glibc' },
{ name: 'tailwindcss-linux-x64-musl', target: 'bun-linux-x64-baseline-musl' },
{ name: 'tailwindcss-macos-arm64', target: 'bun-darwin-arm64' },
{ name: 'tailwindcss-macos-x64', target: 'bun-darwin-x64-baseline' },
{ name: 'tailwindcss-windows-x64.exe', target: 'bun-windows-x64-baseline' },
]

let summary: { target: Bun.Build.Target; name: string; sum: string }[] = []

// Build platform binaries and checksum them.
let start = process.hrtime.bigint()
for (let { target, name } of builds) {
let outfile = path.resolve(__dirname, `../dist/${name}`)

process.env.PLATFORM_LIBC = target.includes('-musl') ? 'musl' : 'glibc'

let result = await Bun.build({
entrypoints: ['./src/index.ts'],
target: 'node',
env: 'inline',
compile: {
target,
outfile,
},
})

let entry = result.outputs.find((output) => output.kind === 'entry-point')
if (!entry) throw new Error(`Build failed for ${target}`)

let content = await readFile(outfile)
let sum = createHash('sha256').update(content).digest('hex')

let elapsed = process.hrtime.bigint() - start

return {
triple,
file,
sum,
elapsed,
}
summary.push({
target,
name,
sum: createHash('sha256').update(content).digest('hex'),
})
}

await mkdir(path.resolve(__dirname, '../dist'), { recursive: true })

// Build platform binaries and checksum them. We use baseline builds for all x64 platforms to ensure
// compatibility with older hardware.
let results = await Promise.all([
build('bun-linux-arm64', './tailwindcss-linux-arm64'),
build('bun-linux-arm64-musl', './tailwindcss-linux-arm64-musl'),

build('bun-linux-x64-baseline', './tailwindcss-linux-x64'),
build('bun-linux-x64-musl-baseline', './tailwindcss-linux-x64-musl'),

build('bun-darwin-arm64', './tailwindcss-macos-arm64'),
build('bun-darwin-x64-baseline', './tailwindcss-macos-x64'),

build('bun-windows-x64-baseline', './tailwindcss-windows-x64.exe'),
])

// Write the checksums to a file
let sumsFile = path.resolve(__dirname, '../dist/sha256sums.txt')
let sums = results.map(({ file, sum }) => `${sum} ${file}`)

console.table(
results.map(({ triple, sum, elapsed }) => ({
triple,
sum,
elapsed: `${(Number(elapsed) / 1e6).toFixed(0)}ms`,
})),
)
let sums = summary.map(({ name, sum }) => `${sum} ./tailwindcss-${name}`)

await writeFile(sumsFile, sums.join('\n') + '\n')

console.table(summary.map(({ target, sum }) => ({ target, sum })))

let elapsed = process.hrtime.bigint() - start
console.log(`Build completed in ${(Number(elapsed) / 1e6).toFixed(0)}ms`)