From 082a16e6762cea36af209ab9d0f0867b2055df87 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:29:26 -0500 Subject: [PATCH 1/7] Rework standalone CLI build setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is equivalent to the old build setup but doesn’t need to use CLI commands to build the binaries --- .../@tailwindcss-standalone/scripts/build.ts | 123 +++++++++--------- 1 file changed, 58 insertions(+), 65 deletions(-) diff --git a/packages/@tailwindcss-standalone/scripts/build.ts b/packages/@tailwindcss-standalone/scripts/build.ts index 584175cfd1fe..12aeac3c2ce0 100644 --- a/packages/@tailwindcss-standalone/scripts/build.ts +++ b/packages/@tailwindcss-standalone/scripts/build.ts @@ -1,4 +1,3 @@ -import { $ } from 'bun' import { createHash } from 'node:crypto' import { mkdir, readFile, writeFile } from 'node:fs/promises' import * as path from 'node:path' @@ -6,80 +5,74 @@ 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 }) - } - } +// Workaround for Bun binary downloads failing on Windows CI when +// USERPROFILE is passed through by Turborepo. +// +// Unfortunately, setting this at runtime doesn't appear to work so we have to +// spawn a new process without the env var. +if (process.env.NESTED_BUILD !== '1' && process.env.USERPROFILE && process.env.USERPROFILE !== '') { + let result = await Bun.$`bun ${fileURLToPath(import.meta.url)}`.env({ + USERPROFILE: '', + NESTED_BUILD: '1', + }) + + process.exit(result.exitCode) } -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)) +// 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' }, + // @ts-expect-error: Either the types are wrong or the runtime needs to be updated + // to accept a `-glibc` at the end like the types suggest. + { name: 'tailwindcss-linux-x64', target: 'bun-linux-x64-baseline' }, + { 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} ./${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`) From 66e19b8bee457259436c241f607fa579c5ffd717 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:26:35 -0500 Subject: [PATCH 2/7] =?UTF-8?q?Don=E2=80=99t=20inline=20all=20env=20vars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right now `NODE_PATH` gets inlined into the build causing linux binaries to additionally search for `/Users/runner/…` directories --- packages/@tailwindcss-standalone/scripts/build.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/@tailwindcss-standalone/scripts/build.ts b/packages/@tailwindcss-standalone/scripts/build.ts index 12aeac3c2ce0..c76d892c4565 100644 --- a/packages/@tailwindcss-standalone/scripts/build.ts +++ b/packages/@tailwindcss-standalone/scripts/build.ts @@ -40,12 +40,16 @@ 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', + + define: { + // This ensures only necessary binaries are bundled for linux targets + // It reduces binary size since no runtime selection is necessary + 'process.env.PLATFORM_LIBC': JSON.stringify(target.includes('-musl') ? 'musl' : 'glibc'), + }, + compile: { target, outfile, From f18da757bb8d66f8de6cd5d2c4ee5c1152165edc Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:27:09 -0500 Subject: [PATCH 3/7] Disable `.env` and `Bunfig.toml` auto loading --- packages/@tailwindcss-standalone/scripts/build.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/@tailwindcss-standalone/scripts/build.ts b/packages/@tailwindcss-standalone/scripts/build.ts index c76d892c4565..8df225ae5ccf 100644 --- a/packages/@tailwindcss-standalone/scripts/build.ts +++ b/packages/@tailwindcss-standalone/scripts/build.ts @@ -53,6 +53,12 @@ for (let { target, name } of builds) { compile: { target, outfile, + + // Disable .env loading + autoloadDotenv: false, + + // Disable bunfig.toml loading + autoloadBunfig: false, }, }) From c7027ac145ca7555b2285fa140a420c0ffc09538 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:27:38 -0500 Subject: [PATCH 4/7] Simplify Oxide setup when inlined into binary --- .../@tailwindcss-standalone/scripts/build.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/@tailwindcss-standalone/scripts/build.ts b/packages/@tailwindcss-standalone/scripts/build.ts index 8df225ae5ccf..acb119b9694a 100644 --- a/packages/@tailwindcss-standalone/scripts/build.ts +++ b/packages/@tailwindcss-standalone/scripts/build.ts @@ -48,6 +48,12 @@ for (let { target, name } of builds) { // This ensures only necessary binaries are bundled for linux targets // It reduces binary size since no runtime selection is necessary 'process.env.PLATFORM_LIBC': JSON.stringify(target.includes('-musl') ? 'musl' : 'glibc'), + + // This prevents the WASI build from being bundled with the binary + 'process.env.NAPI_RS_FORCE_WASI': JSON.stringify(''), + + // This simplifies the Oxide loading code a small amount + 'process.env.NAPI_RS_NATIVE_LIBRARY_PATH': JSON.stringify(''), }, compile: { @@ -60,6 +66,17 @@ for (let { target, name } of builds) { // Disable bunfig.toml loading autoloadBunfig: false, }, + + plugins: [ + { + name: 'tailwindcss-plugin', + setup(build) { + build.onLoad({ filter: /tailwindcss-oxide\.wasi\.cjs$/ }, async (args) => { + return { contents: '' } + }) + }, + }, + ], }) let entry = result.outputs.find((output) => output.kind === 'entry-point') From c5a3565727eb80b77ab0bb6762e11ade1f4c03b6 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:28:25 -0500 Subject: [PATCH 5/7] =?UTF-8?q?Don=E2=80=99t=20support=20`NODE=5FPATH`=20e?= =?UTF-8?q?nv=20in=20standalone=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@tailwindcss-standalone/scripts/build.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@tailwindcss-standalone/scripts/build.ts b/packages/@tailwindcss-standalone/scripts/build.ts index acb119b9694a..5ca9a33200e9 100644 --- a/packages/@tailwindcss-standalone/scripts/build.ts +++ b/packages/@tailwindcss-standalone/scripts/build.ts @@ -54,6 +54,9 @@ for (let { target, name } of builds) { // This simplifies the Oxide loading code a small amount 'process.env.NAPI_RS_NATIVE_LIBRARY_PATH': JSON.stringify(''), + + // No need to support additional NODE_PATHs in the standalone build + 'process.env.NODE_PATH': JSON.stringify(''), }, compile: { From 43cfb2dcbe27980448c47c91086e9f3f8fe5613c Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:29:02 -0500 Subject: [PATCH 6/7] Enable minification --- packages/@tailwindcss-standalone/scripts/build.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/@tailwindcss-standalone/scripts/build.ts b/packages/@tailwindcss-standalone/scripts/build.ts index 5ca9a33200e9..7ff572d3b0b6 100644 --- a/packages/@tailwindcss-standalone/scripts/build.ts +++ b/packages/@tailwindcss-standalone/scripts/build.ts @@ -43,6 +43,12 @@ for (let { target, name } of builds) { let result = await Bun.build({ entrypoints: ['./src/index.ts'], target: 'node', + minify: { + whitespace: false, + syntax: true, + identifiers: false, + keepNames: true, + }, define: { // This ensures only necessary binaries are bundled for linux targets From 9a2048bfe190186572bf4dbfb7621ab85a071210 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 30 Nov 2025 08:29:35 -0500 Subject: [PATCH 7/7] Remove `__tw_readFile` hack Async APIs became capable of reading embedded files in Bun v1.2.3 --- packages/@tailwindcss-node/src/compile.ts | 11 ----------- packages/@tailwindcss-standalone/src/index.ts | 10 ---------- 2 files changed, 21 deletions(-) diff --git a/packages/@tailwindcss-node/src/compile.ts b/packages/@tailwindcss-node/src/compile.ts index ac7cc6f15211..7eb959d3bb1b 100644 --- a/packages/@tailwindcss-node/src/compile.ts +++ b/packages/@tailwindcss-node/src/compile.ts @@ -166,17 +166,6 @@ async function loadStylesheet( onDependency(resolvedPath) - if (typeof globalThis.__tw_readFile === 'function') { - let file = await globalThis.__tw_readFile(resolvedPath, 'utf-8') - if (file) { - return { - path: resolvedPath, - base: path.dirname(resolvedPath), - content: file, - } - } - } - let file = await fsPromises.readFile(resolvedPath, 'utf-8') return { path: resolvedPath, diff --git a/packages/@tailwindcss-standalone/src/index.ts b/packages/@tailwindcss-standalone/src/index.ts index be85729bb678..ce6ba84d11fe 100644 --- a/packages/@tailwindcss-standalone/src/index.ts +++ b/packages/@tailwindcss-standalone/src/index.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs' import { createRequire } from 'node:module' import packageJson from 'tailwindcss/package.json' @@ -61,15 +60,6 @@ globalThis.__tw_load = async (id) => { } } globalThis.__tw_version = packageJson.version -globalThis.__tw_readFile = async (path, encoding) => { - // When reading a file from the `$bunfs`, we need to use the synchronous - // `readFileSync` API - let isEmbeddedFileBase = path.includes('/$bunfs/root') || path.includes(':/~BUN/root') - if (!isEmbeddedFileBase) { - return - } - return fs.readFileSync(path, encoding) -} // We use a plugin to make sure that the JS APIs are bundled with the standalone // CLI and can be imported inside configs and plugins