Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursed, I love it.

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