tailwindcss/packages/@tailwindcss-standalone/scripts/build.ts

112 lines
3.7 KiB
TypeScript
Raw Normal View History

Add standalone CLI (#14270) This PR adds a new standalone client: A single-binary file that you can use to run Tailwind v4 without having a node setup. To make this work we use Bun's single-binary build which can properly package up native modules and the bun runtime for us so we do not have to rely on any expand-into-tmp-folder-at-runtime workarounds. When running locally, `pnpm build` will now standalone artifacts inside `packages/@tailwindcss-standalone/dist`. Note that since we do not build Oxide for other environments in the local setup, you won't be able to use the standalone artifacts for other platforms in local dev mode. Unfortunately Bun does not have support for Windows ARM builds yet but we found that using the `bun-baseline` runtime for Windows x64 would make the builds work fine in ARM emulation mode: ![Screenshot windows](https://github.com/user-attachments/assets/5b39387f-ec50-4757-9469-19b98e43162d) Some Bun related issues we faced and worked around: - We found that the regular Windows x64 build of `bun` does not run on Windows ARM via emulation. Instead, we have to use the `bun-baseline` builds which emulate correctly. - When we tried to bundle artifacts with [embed directories](https://bun.sh/docs/bundler/executables#embed-directories), node binary dependencies were no longer resolved correctly even though they would still be bundled and accessible within the [`embeddedFiles` list](https://bun.sh/docs/bundler/executables#listing-embedded-files). We worked around this by using the `import * as from ... with { type: "file" };` and patching the resolver we use in our CLI. - If you have an import to a module that is used as a regular import _and_ a `with { type: "file" }`, it will either return the module in both cases _or_ the file path when we would expect only the `with { type: "file" }` import to return the path. We do read the Tailwind CSS version via the file system and `require.resolve()` in the CLI and via `import * from './package.json'` in core and had to work around this by patching the version resolution in our CLI. ```ts import packageJson from "./package.json" import packageJsonPath from "./package.json" with {type: "file"} // We do not expect these to be equal packageJson === packageJsonPath ``` - We can not customize the app icon used for Windows `.exe` builds without decompiling the binary. For now we will leave the default but one workaround is to [use tools like ResourceHacker](https://github.com/tailwindlabs/tailwindcss/pull/14270/commits/698d9c4bd182391adb4b236f19a539aff13f78f9) to decompile the binary first. --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-09-02 15:23:46 +02:00
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))
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.
2025-12-08 11:23:20 -05:00
// 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)
Add standalone CLI (#14270) This PR adds a new standalone client: A single-binary file that you can use to run Tailwind v4 without having a node setup. To make this work we use Bun's single-binary build which can properly package up native modules and the bun runtime for us so we do not have to rely on any expand-into-tmp-folder-at-runtime workarounds. When running locally, `pnpm build` will now standalone artifacts inside `packages/@tailwindcss-standalone/dist`. Note that since we do not build Oxide for other environments in the local setup, you won't be able to use the standalone artifacts for other platforms in local dev mode. Unfortunately Bun does not have support for Windows ARM builds yet but we found that using the `bun-baseline` runtime for Windows x64 would make the builds work fine in ARM emulation mode: ![Screenshot windows](https://github.com/user-attachments/assets/5b39387f-ec50-4757-9469-19b98e43162d) Some Bun related issues we faced and worked around: - We found that the regular Windows x64 build of `bun` does not run on Windows ARM via emulation. Instead, we have to use the `bun-baseline` builds which emulate correctly. - When we tried to bundle artifacts with [embed directories](https://bun.sh/docs/bundler/executables#embed-directories), node binary dependencies were no longer resolved correctly even though they would still be bundled and accessible within the [`embeddedFiles` list](https://bun.sh/docs/bundler/executables#listing-embedded-files). We worked around this by using the `import * as from ... with { type: "file" };` and patching the resolver we use in our CLI. - If you have an import to a module that is used as a regular import _and_ a `with { type: "file" }`, it will either return the module in both cases _or_ the file path when we would expect only the `with { type: "file" }` import to return the path. We do read the Tailwind CSS version via the file system and `require.resolve()` in the CLI and via `import * from './package.json'` in core and had to work around this by patching the version resolution in our CLI. ```ts import packageJson from "./package.json" import packageJsonPath from "./package.json" with {type: "file"} // We do not expect these to be equal packageJson === packageJsonPath ``` - We can not customize the app icon used for Windows `.exe` builds without decompiling the binary. For now we will leave the default but one workaround is to [use tools like ResourceHacker](https://github.com/tailwindlabs/tailwindcss/pull/14270/commits/698d9c4bd182391adb4b236f19a539aff13f78f9) to decompile the binary first. --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-09-02 15:23:46 +02: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.
2025-12-08 11:23:20 -05:00
// 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(''),
},
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}`)
Add standalone CLI (#14270) This PR adds a new standalone client: A single-binary file that you can use to run Tailwind v4 without having a node setup. To make this work we use Bun's single-binary build which can properly package up native modules and the bun runtime for us so we do not have to rely on any expand-into-tmp-folder-at-runtime workarounds. When running locally, `pnpm build` will now standalone artifacts inside `packages/@tailwindcss-standalone/dist`. Note that since we do not build Oxide for other environments in the local setup, you won't be able to use the standalone artifacts for other platforms in local dev mode. Unfortunately Bun does not have support for Windows ARM builds yet but we found that using the `bun-baseline` runtime for Windows x64 would make the builds work fine in ARM emulation mode: ![Screenshot windows](https://github.com/user-attachments/assets/5b39387f-ec50-4757-9469-19b98e43162d) Some Bun related issues we faced and worked around: - We found that the regular Windows x64 build of `bun` does not run on Windows ARM via emulation. Instead, we have to use the `bun-baseline` builds which emulate correctly. - When we tried to bundle artifacts with [embed directories](https://bun.sh/docs/bundler/executables#embed-directories), node binary dependencies were no longer resolved correctly even though they would still be bundled and accessible within the [`embeddedFiles` list](https://bun.sh/docs/bundler/executables#listing-embedded-files). We worked around this by using the `import * as from ... with { type: "file" };` and patching the resolver we use in our CLI. - If you have an import to a module that is used as a regular import _and_ a `with { type: "file" }`, it will either return the module in both cases _or_ the file path when we would expect only the `with { type: "file" }` import to return the path. We do read the Tailwind CSS version via the file system and `require.resolve()` in the CLI and via `import * from './package.json'` in core and had to work around this by patching the version resolution in our CLI. ```ts import packageJson from "./package.json" import packageJsonPath from "./package.json" with {type: "file"} // We do not expect these to be equal packageJson === packageJsonPath ``` - We can not customize the app icon used for Windows `.exe` builds without decompiling the binary. For now we will leave the default but one workaround is to [use tools like ResourceHacker](https://github.com/tailwindlabs/tailwindcss/pull/14270/commits/698d9c4bd182391adb4b236f19a539aff13f78f9) to decompile the binary first. --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-09-02 15:23:46 +02:00
let content = await readFile(outfile)
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.
2025-12-08 11:23:20 -05:00
summary.push({
target,
name,
sum: createHash('sha256').update(content).digest('hex'),
})
Add standalone CLI (#14270) This PR adds a new standalone client: A single-binary file that you can use to run Tailwind v4 without having a node setup. To make this work we use Bun's single-binary build which can properly package up native modules and the bun runtime for us so we do not have to rely on any expand-into-tmp-folder-at-runtime workarounds. When running locally, `pnpm build` will now standalone artifacts inside `packages/@tailwindcss-standalone/dist`. Note that since we do not build Oxide for other environments in the local setup, you won't be able to use the standalone artifacts for other platforms in local dev mode. Unfortunately Bun does not have support for Windows ARM builds yet but we found that using the `bun-baseline` runtime for Windows x64 would make the builds work fine in ARM emulation mode: ![Screenshot windows](https://github.com/user-attachments/assets/5b39387f-ec50-4757-9469-19b98e43162d) Some Bun related issues we faced and worked around: - We found that the regular Windows x64 build of `bun` does not run on Windows ARM via emulation. Instead, we have to use the `bun-baseline` builds which emulate correctly. - When we tried to bundle artifacts with [embed directories](https://bun.sh/docs/bundler/executables#embed-directories), node binary dependencies were no longer resolved correctly even though they would still be bundled and accessible within the [`embeddedFiles` list](https://bun.sh/docs/bundler/executables#listing-embedded-files). We worked around this by using the `import * as from ... with { type: "file" };` and patching the resolver we use in our CLI. - If you have an import to a module that is used as a regular import _and_ a `with { type: "file" }`, it will either return the module in both cases _or_ the file path when we would expect only the `with { type: "file" }` import to return the path. We do read the Tailwind CSS version via the file system and `require.resolve()` in the CLI and via `import * from './package.json'` in core and had to work around this by patching the version resolution in our CLI. ```ts import packageJson from "./package.json" import packageJsonPath from "./package.json" with {type: "file"} // We do not expect these to be equal packageJson === packageJsonPath ``` - We can not customize the app icon used for Windows `.exe` builds without decompiling the binary. For now we will leave the default but one workaround is to [use tools like ResourceHacker](https://github.com/tailwindlabs/tailwindcss/pull/14270/commits/698d9c4bd182391adb4b236f19a539aff13f78f9) to decompile the binary first. --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-09-02 15:23:46 +02:00
}
await mkdir(path.resolve(__dirname, '../dist'), { recursive: true })
// Write the checksums to a file
let sumsFile = path.resolve(__dirname, '../dist/sha256sums.txt')
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.
2025-12-08 11:23:20 -05:00
let sums = summary.map(({ name, sum }) => `${sum} ./${name}`)
Add standalone CLI (#14270) This PR adds a new standalone client: A single-binary file that you can use to run Tailwind v4 without having a node setup. To make this work we use Bun's single-binary build which can properly package up native modules and the bun runtime for us so we do not have to rely on any expand-into-tmp-folder-at-runtime workarounds. When running locally, `pnpm build` will now standalone artifacts inside `packages/@tailwindcss-standalone/dist`. Note that since we do not build Oxide for other environments in the local setup, you won't be able to use the standalone artifacts for other platforms in local dev mode. Unfortunately Bun does not have support for Windows ARM builds yet but we found that using the `bun-baseline` runtime for Windows x64 would make the builds work fine in ARM emulation mode: ![Screenshot windows](https://github.com/user-attachments/assets/5b39387f-ec50-4757-9469-19b98e43162d) Some Bun related issues we faced and worked around: - We found that the regular Windows x64 build of `bun` does not run on Windows ARM via emulation. Instead, we have to use the `bun-baseline` builds which emulate correctly. - When we tried to bundle artifacts with [embed directories](https://bun.sh/docs/bundler/executables#embed-directories), node binary dependencies were no longer resolved correctly even though they would still be bundled and accessible within the [`embeddedFiles` list](https://bun.sh/docs/bundler/executables#listing-embedded-files). We worked around this by using the `import * as from ... with { type: "file" };` and patching the resolver we use in our CLI. - If you have an import to a module that is used as a regular import _and_ a `with { type: "file" }`, it will either return the module in both cases _or_ the file path when we would expect only the `with { type: "file" }` import to return the path. We do read the Tailwind CSS version via the file system and `require.resolve()` in the CLI and via `import * from './package.json'` in core and had to work around this by patching the version resolution in our CLI. ```ts import packageJson from "./package.json" import packageJsonPath from "./package.json" with {type: "file"} // We do not expect these to be equal packageJson === packageJsonPath ``` - We can not customize the app icon used for Windows `.exe` builds without decompiling the binary. For now we will leave the default but one workaround is to [use tools like ResourceHacker](https://github.com/tailwindlabs/tailwindcss/pull/14270/commits/698d9c4bd182391adb4b236f19a539aff13f78f9) to decompile the binary first. --------- Co-authored-by: Jordan Pittman <jordan@cryptica.me> Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-09-02 15:23:46 +02:00
await writeFile(sumsFile, sums.join('\n') + '\n')
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.
2025-12-08 11:23:20 -05:00
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`)