Skip to content
11 changes: 0 additions & 11 deletions packages/@tailwindcss-node/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/@tailwindcss-standalone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
"@parcel/watcher-linux-x64-glibc": "^2.5.1",
"@parcel/watcher-linux-x64-musl": "^2.5.1",
"@parcel/watcher-win32-x64": "^2.5.1",
"@types/bun": "^1.3.2",
"bun": "^1.3.2",
"@types/bun": "^1.3.3",
"bun": "^1.3.3",
"lightningcss-darwin-arm64": "catalog:",
"lightningcss-darwin-x64": "catalog:",
"lightningcss-linux-arm64-gnu": "catalog:",
Expand Down
159 changes: 94 additions & 65 deletions packages/@tailwindcss-standalone/scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,114 @@
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 })
}
}
// 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}`)

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
// 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(''),

// No need to support additional NODE_PATHs in the standalone build
'process.env.NODE_PATH': JSON.stringify(''),
},

compile: {
target,
outfile,

// Disable .env loading
autoloadDotenv: false,

// 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')
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`)
10 changes: 0 additions & 10 deletions packages/@tailwindcss-standalone/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import fs from 'node:fs'
import { createRequire } from 'node:module'
import packageJson from 'tailwindcss/package.json'

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion playgrounds/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"devDependencies": {
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.3",
"bun": "^1.3.2",
"bun": "^1.3.3",
"vite": "catalog:"
}
}
Loading