mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Don't inline all env vars in Standalone CLI (#19391)
Fixes #19389 We inlined env vars in the Standalone CLI because we use some custom patches + env vars to ensure that only the appropriate `glibc` / `musl` binaries are included for Lightning CSS and Parcel Watcher for Linux builds. The build happens to run on a macOS Github CI machine though so `NODE_PATH` was getting inlined as the string: ``` /Users/runner/work/tailwindcss/tailwindcss/node_modules/.pnpm/bun@1.3.0/node_modules/bun/bin/node_modules ``` I don't think there's a reason for `NODE_PATH` to work on the Standalone CLI (and it didn't work because of the above bug *anyway*) so I've done a few things here: 1. The build setup now uses `Bun.build(…)` which now supports compiling binaries. This speeds up the build process a bit. 2. We're no longer inlining all env vars. We selectively inline only a few using `define`. 3. I've explicitly disabled the extra `NODE_PATH` support in `@tailwindcss/node` when building with the Standalone CLI. 4. The `__tw_readFile` hack is now gone. Async FS APIs were not originally able to read embedded files but that changed in Bun v1.2.3 making the hack unnecessary. 5. A few more env vars are now inlined + a plugin to simplify the Oxide loading code when bundled. 6. A plugin + env vars prevents bundling WASI build as it's not necessary for the Standalone CLI. I want to find a way to get rid of `__tw_resolve` and `__tw_load` but don't want to change too much in this PR so I haven't looked into it yet.
This commit is contained in:
parent
820d90797c
commit
cdc851d3f8
@ -166,17 +166,6 @@ async function loadStylesheet(
|
|||||||
|
|
||||||
onDependency(resolvedPath)
|
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')
|
let file = await fsPromises.readFile(resolvedPath, 'utf-8')
|
||||||
return {
|
return {
|
||||||
path: resolvedPath,
|
path: resolvedPath,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { $ } from 'bun'
|
|
||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'node:crypto'
|
||||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
@ -6,80 +5,110 @@ import { fileURLToPath } from 'node:url'
|
|||||||
|
|
||||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||||
|
|
||||||
async function buildForPlatform(triple: string, outfile: string) {
|
// Workaround for Bun binary downloads failing on Windows CI when
|
||||||
// We wrap this in a retry because occasionally the atomic rename fails for some reason
|
// USERPROFILE is passed through by Turborepo.
|
||||||
for (let i = 0; i < 5; ++i) {
|
//
|
||||||
try {
|
// Unfortunately, setting this at runtime doesn't appear to work so we have to
|
||||||
let cmd = $`bun build --compile --target=${triple} ./src/index.ts --outfile=${outfile} --env inline`
|
// 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',
|
||||||
|
})
|
||||||
|
|
||||||
// This env var is used by our patched versions of Lightning CSS and Parcel Watcher to
|
process.exit(result.exitCode)
|
||||||
// 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) {
|
// We use baseline builds for all x64 platforms to ensure compatibility with
|
||||||
let start = process.hrtime.bigint()
|
// 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 outfile = path.resolve(__dirname, `../dist/${file}`)
|
let summary: { target: Bun.Build.Target; name: string; sum: string }[] = []
|
||||||
|
|
||||||
await buildForPlatform(triple, outfile)
|
// Build platform binaries and checksum them.
|
||||||
|
let start = process.hrtime.bigint()
|
||||||
|
for (let { target, name } of builds) {
|
||||||
|
let outfile = path.resolve(__dirname, `../dist/${name}`)
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
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 content = await readFile(outfile)
|
||||||
let sum = createHash('sha256').update(content).digest('hex')
|
|
||||||
|
|
||||||
let elapsed = process.hrtime.bigint() - start
|
summary.push({
|
||||||
|
target,
|
||||||
return {
|
name,
|
||||||
triple,
|
sum: createHash('sha256').update(content).digest('hex'),
|
||||||
file,
|
})
|
||||||
sum,
|
|
||||||
elapsed,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdir(path.resolve(__dirname, '../dist'), { recursive: true })
|
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
|
// Write the checksums to a file
|
||||||
let sumsFile = path.resolve(__dirname, '../dist/sha256sums.txt')
|
let sumsFile = path.resolve(__dirname, '../dist/sha256sums.txt')
|
||||||
let sums = results.map(({ file, sum }) => `${sum} ${file}`)
|
let sums = summary.map(({ name, sum }) => `${sum} ./${name}`)
|
||||||
|
|
||||||
console.table(
|
|
||||||
results.map(({ triple, sum, elapsed }) => ({
|
|
||||||
triple,
|
|
||||||
sum,
|
|
||||||
elapsed: `${(Number(elapsed) / 1e6).toFixed(0)}ms`,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
await writeFile(sumsFile, sums.join('\n') + '\n')
|
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`)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import fs from 'node:fs'
|
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import packageJson from 'tailwindcss/package.json'
|
import packageJson from 'tailwindcss/package.json'
|
||||||
|
|
||||||
@ -61,15 +60,6 @@ globalThis.__tw_load = async (id) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
globalThis.__tw_version = packageJson.version
|
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
|
// 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
|
// CLI and can be imported inside configs and plugins
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user