tailwindcss/scripts/pack-packages.mjs

68 lines
1.8 KiB
JavaScript
Raw Permalink Normal View History

Add integration test setup and tests for the Vite integration (#14089) This PR adds a new root `/integrations` folder that will be the home of integration tests. The idea of these tests is to use Tailwind in various setups just like our users would (by only using the publishable npm builds). To avoid issues with concurrent tests making changes to the file system, to make it very easy to test through a range of versions, and to avoid changing configuration objects over and over in test runs, we decided to inline the scaffolding completely into the test file and have no examples checked into the repo. Here's an example of how this can look like for a simple Vite test: ```ts test('works with production builds', { fs: { 'package.json': json` { "type": "module", "dependencies": { "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^" }, "devDependencies": { "vite": "^5.3.5" } } `, 'vite.config.ts': ts` import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' export default defineConfig({ build: { cssMinify: false }, plugins: [tailwindcss()], }) `, 'index.html': html` <head> <link rel="stylesheet" href="./src/index.css"> </head> <body> <div class="underline m-2">Hello, world!</div> </body> `, 'src/index.css': css` @import 'tailwindcss/theme' reference; @import 'tailwindcss/utilities'; `, }, }, async ({ fs, exec }) => { await exec('pnpm vite build') expect.assertions(2) for (let [path, content] of await fs.glob('dist/**/*.css')) { expect(path).toMatch(/\.css$/) expect(stripTailwindComment(content)).toMatchInlineSnapshot( ` ".m-2 { margin: var(--spacing-2, .5rem); } .underline { text-decoration-line: underline; }" `, ) } }, ) ``` By defining all dependencies this way, we never have to worry about which fixtures are checked in and can more easily describe changes to the setup. For ergonomics, we've also added the [`embed` prettier plugin](https://github.com/Sec-ant/prettier-plugin-embed). This will mean that files inlined in the `fs` setup are properly indented. No extra work needed! If you're using VS Code, I can also recommend the [Language Literals](https://marketplace.visualstudio.com/items?itemName=sissel.language-literals) extension so that syntax highlighting also _just works_. A neat feature of inlining the scaffolding like this is to make it very simple to test through a variety of versions. For example, here's how we can set up a test against Vite 5 and Vite 4: ```js ;['^4.5.3', '^5.3.5'].forEach(viteVersion => { test(`works with production builds for Vite ${viteVersion}`, { fs: { 'package.json': json` { "type": "module", "devDependencies": { "vite": "${viteVersion}" } } `, async () => { // Do something }, ) }) ``` ## Philosophy Before we dive into the specifics, I want to clearly state the design considerations we have chosen for this new test suite: - All file mutations should be done in temp folders, nothing should ever mess with your working directory - Windows as a first-class citizen - Have a clean and simple API that describes the test setup only using public APIs - Focus on reliability (make sure cleanup scripts work and are tolerant to various error scenarios) - If a user reports an issue with a specific configuration, we want to be able to reproduce them with integration tests, no matter how obscure the setup (this means the test need to be in control of most of the variables) - Tests should be reasonably fast (obviously this depends on the integration. If we use a slow build tool, we can't magically speed it up, but our overhead should be minimal). ## How it works The current implementation provides a custom `test` helper function that, when used, sets up the environment according to the configuration. It'll create a new temporary directory and create all files, ensuring things like proper `\r\n` line endings on Windows. We do have to patch the `package.json` specifically, since we can not use public versions of the tailwindcss packages as we want to be able to test against a development build. To make this happen, every `pnpm build` run now creates tarballs of the npm modules (that contain only the files that would also in the published build). We then patch the `package.json` to rewrite `workspace:^` versions to link to those tarballs. We found this to work reliably on Windows and macOS as well as being fast enough to not cause any issues. Furthermore we also decided to use `pnpm` as the version manager for integration tests because of it's global module cache (so installing `vite` is fast as soon as you installed it once). The test function will receive a few utilities that it can use to more easily interact with the temp dir. One example is a `fs.glob` function that you can use to easily find files in eventual `dist/` directories or helpers around `spawn` and `exec` that make sure that processes are cleaned up correctly. Because we use tarballs from our build dependencies, working on changes requires a workflow where you run `pnpm build` before running `pnpm test:integrations`. However it also means we can run clients like our CLI client with no additional overhead—just install the dependency like any user would and set up your test cases this way. ## Test plan This PR also includes two Vite specific integration tests: One testing a static build (`pnpm vite build`) and one a dev mode build (`pnpm vite dev`) that also makes changes to the file system and asserts that the resources properly update. --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-08-02 11:50:49 +02:00
import { exec, execSync } from 'node:child_process'
import fs from 'node:fs/promises'
import { platform } from 'node:os'
import path, { dirname } from 'node:path'
import url from 'node:url'
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
let root = path.resolve(__dirname, '..')
let command = platform() === 'win32' ? 'cd' : 'pwd'
let rawPaths = execSync(`pnpm --silent --filter=!./playgrounds/* -r exec ${command}`).toString()
let paths = rawPaths
.trim()
.split(/\r?\n/)
.map((x) => path.join(x, 'package.json'))
let workspaces = new Map()
// Track all the workspaces
for (let path of paths) {
let pkg = await fs.readFile(path, 'utf8').then(JSON.parse)
if (pkg.private) continue
workspaces.set(pkg.name, { version: pkg.version ?? '', dir: dirname(path) })
}
// Clean dist folder
await fs.rm(path.join(root, 'dist'), { recursive: true, force: true })
Promise.all(
[...workspaces.entries()].map(async ([name, { version, dir }]) => {
function pack() {
return new Promise((resolve) => {
exec(
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
`pnpm pack --pack-gzip-level=0 --pack-destination="${path.join(root, 'dist').replace(/\\/g, '\\\\')}"`,
Add integration test setup and tests for the Vite integration (#14089) This PR adds a new root `/integrations` folder that will be the home of integration tests. The idea of these tests is to use Tailwind in various setups just like our users would (by only using the publishable npm builds). To avoid issues with concurrent tests making changes to the file system, to make it very easy to test through a range of versions, and to avoid changing configuration objects over and over in test runs, we decided to inline the scaffolding completely into the test file and have no examples checked into the repo. Here's an example of how this can look like for a simple Vite test: ```ts test('works with production builds', { fs: { 'package.json': json` { "type": "module", "dependencies": { "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^" }, "devDependencies": { "vite": "^5.3.5" } } `, 'vite.config.ts': ts` import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' export default defineConfig({ build: { cssMinify: false }, plugins: [tailwindcss()], }) `, 'index.html': html` <head> <link rel="stylesheet" href="./src/index.css"> </head> <body> <div class="underline m-2">Hello, world!</div> </body> `, 'src/index.css': css` @import 'tailwindcss/theme' reference; @import 'tailwindcss/utilities'; `, }, }, async ({ fs, exec }) => { await exec('pnpm vite build') expect.assertions(2) for (let [path, content] of await fs.glob('dist/**/*.css')) { expect(path).toMatch(/\.css$/) expect(stripTailwindComment(content)).toMatchInlineSnapshot( ` ".m-2 { margin: var(--spacing-2, .5rem); } .underline { text-decoration-line: underline; }" `, ) } }, ) ``` By defining all dependencies this way, we never have to worry about which fixtures are checked in and can more easily describe changes to the setup. For ergonomics, we've also added the [`embed` prettier plugin](https://github.com/Sec-ant/prettier-plugin-embed). This will mean that files inlined in the `fs` setup are properly indented. No extra work needed! If you're using VS Code, I can also recommend the [Language Literals](https://marketplace.visualstudio.com/items?itemName=sissel.language-literals) extension so that syntax highlighting also _just works_. A neat feature of inlining the scaffolding like this is to make it very simple to test through a variety of versions. For example, here's how we can set up a test against Vite 5 and Vite 4: ```js ;['^4.5.3', '^5.3.5'].forEach(viteVersion => { test(`works with production builds for Vite ${viteVersion}`, { fs: { 'package.json': json` { "type": "module", "devDependencies": { "vite": "${viteVersion}" } } `, async () => { // Do something }, ) }) ``` ## Philosophy Before we dive into the specifics, I want to clearly state the design considerations we have chosen for this new test suite: - All file mutations should be done in temp folders, nothing should ever mess with your working directory - Windows as a first-class citizen - Have a clean and simple API that describes the test setup only using public APIs - Focus on reliability (make sure cleanup scripts work and are tolerant to various error scenarios) - If a user reports an issue with a specific configuration, we want to be able to reproduce them with integration tests, no matter how obscure the setup (this means the test need to be in control of most of the variables) - Tests should be reasonably fast (obviously this depends on the integration. If we use a slow build tool, we can't magically speed it up, but our overhead should be minimal). ## How it works The current implementation provides a custom `test` helper function that, when used, sets up the environment according to the configuration. It'll create a new temporary directory and create all files, ensuring things like proper `\r\n` line endings on Windows. We do have to patch the `package.json` specifically, since we can not use public versions of the tailwindcss packages as we want to be able to test against a development build. To make this happen, every `pnpm build` run now creates tarballs of the npm modules (that contain only the files that would also in the published build). We then patch the `package.json` to rewrite `workspace:^` versions to link to those tarballs. We found this to work reliably on Windows and macOS as well as being fast enough to not cause any issues. Furthermore we also decided to use `pnpm` as the version manager for integration tests because of it's global module cache (so installing `vite` is fast as soon as you installed it once). The test function will receive a few utilities that it can use to more easily interact with the temp dir. One example is a `fs.glob` function that you can use to easily find files in eventual `dist/` directories or helpers around `spawn` and `exec` that make sure that processes are cleaned up correctly. Because we use tarballs from our build dependencies, working on changes requires a workflow where you run `pnpm build` before running `pnpm test:integrations`. However it also means we can run clients like our CLI client with no additional overhead—just install the dependency like any user would and set up your test cases this way. ## Test plan This PR also includes two Vite specific integration tests: One testing a static build (`pnpm vite build`) and one a dev mode build (`pnpm vite dev`) that also makes changes to the file system and asserts that the resources properly update. --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-08-02 11:50:49 +02:00
{ cwd: dir },
(err, stdout, stderr) => {
if (err) {
console.error(err, stdout, stderr)
}
resolve(lastLine(stdout.trim()))
},
)
})
}
let filename = await pack()
// Remove version suffix
await fs.rename(
path.join(root, 'dist', path.basename(filename)),
path.join(root, 'dist', pkgToFilename(name)),
)
}),
).then(() => {
console.log('Done.')
})
function pkgToFilename(name) {
return `${name.replace('@', '').replace('/', '-')}.tgz`
}
function lastLine(str) {
let index = str.lastIndexOf('\n')
if (index === -1) return str
return str.slice(index + 1)
}