tailwind2-8 Exercise 1: Run the Upgrade Tool and Measure What Is Left
=====================================================================
Setup: a folder "proj" with npm install tailwindcss@3 postcss autoprefixer @tailwindcss/typography@0.5 (I had 3.4.19 and typography 0.5.20). Add a .gitignore containing node_modules BEFORE the first git add,
then git init, git add -A, git commit. Files:
--- tailwind.config.js ---
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: ['./index.html', './src/**/*.{html,js}'],
safelist: ['bg-emerald-500','bg-pink-' + '500'],
theme: {
extend: {
colors: { brand: { 500: '#4f46e5', 600: '#4338ca' }, surface: '#f1f5f9' },
fontFamily: { display: ['Georgia', 'serif'] },
screens: { '3xl': '1920px' },
borderRadius: { card: '0.75rem' },
},
},
plugins: [require('@tailwindcss/typography')],
};
--- postcss.config.js ---
module.exports = { plugins: { tailwindcss: {}, autoprefixer: {} } };
--- src/input.css ---
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 { @apply text-2xl font-bold; }
}
@layer components {
.btn { @apply inline-flex items-center rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-white hover:bg-brand-600 focus:outline-none focus:ring-2; }
.panel { background: theme('colors.surface'); border-radius: theme('borderRadius.card'); padding: theme(spacing.4); }
}
--- index.html ---
Migration demo
A small page using several things that changed in v4.
Important and ellipsis
safelisted + transform
Prose
Typography plugin text.
Build the baseline: npx tailwindcss -i src/input.css -o dist/out.css
Baseline screenshot: chrome.exe --headless=new --disable-gpu --hide-scrollbars --user-data-dir= --window-size=700,700 --screenshot=v3.png file:///.../proj/index.html
--- probe.mjs (prints computed styles; uses my own DevTools driver cdp.mjs, so in practice read the same values in DevTools) ---
import { withPage } from '../c4/cdp.mjs';
const dir = process.argv[2];
await withPage(`file:///C:/lwdj/tw2/c8/${dir}/index.html`, async (p) => {
const g = (s, q) => p.css(s, q);
console.log('b3 border-color ', await g('#b3','border-top-color'), '| border-width', await g('#b3','border-top-width'));
console.log('b3 border-radius ', await g('#b3','border-top-left-radius'));
console.log('b3 box-shadow ', (await g('#b3','box-shadow')).slice(0,140));
console.log('b3 outline ', await g('#b3','outline-style'), await g('#b3','outline-width'));
console.log('b3 flex-grow ', await g('#b3','flex-grow'));
console.log('b2 background ', await g('#b2','background-color'));
console.log('btn (first) bg ', await g('.btn','background-color'));
console.log('panel bg / radius ', await g('.panel','background-color'), await g('.panel','border-top-left-radius'));
console.log('h1 font-size / family', await g('h1','font-size'), (await g('h1','font-family')).slice(0,20));
console.log('space-x: btn1 margin-right / btn2 margin-left', await g('.btn','margin-right'), await g('#b2','margin-left'));
console.log('ellipsis text-overflow', await g('p.text-brand-600','text-overflow'), '| !font-bold weight', await g('p.text-brand-600','font-weight'));
console.log('gradient bg-image ', (await g('.h-8','background-image')).slice(0,60));
console.log('safelisted/transform ', await g('.rounded-card','background-color'), await g('.rounded-card','transform'));
console.log('prose h2 font-size ', await g('.prose h2','font-size'));
console.log('body bg ', await g('body','background-color'));
});
--- pix.mjs (npm install pngjs pixelmatch) ---
import fs from 'node:fs'; import { PNG } from 'pngjs'; import pixelmatch from 'pixelmatch';
const [a,b,out]=process.argv.slice(2); const A=PNG.sync.read(fs.readFileSync(a)),B=PNG.sync.read(fs.readFileSync(b));
const d=new PNG({width:A.width,height:A.height}); const n=pixelmatch(A.data,B.data,d.data,A.width,A.height,{threshold:0.1});
if(out) fs.writeFileSync(out,PNG.sync.write(d)); console.log(a,'vs',b,'differing pixels',n,'of',A.width*A.height);
Copy the project to "up" (cp -r, including node_modules), then in "up":
npx @tailwindcss/upgrade
npm install @tailwindcss/cli@4
node node_modules/@tailwindcss/cli/dist/index.mjs -i src/input.css -o dist/out.css exit 0, no warnings
Dirty-tree check (a second copy with one uncommitted edit):
npx @tailwindcss/upgrade -> "Git directory is not clean. Please stash or commit your changes before migrating." "You may use the `--force` flag ..." exit code 1, nothing changed
Upgrade output (25 s): linked tailwind.config.js to src/input.css; "could not be automatically migrated to the new CSS configuration format, so your CSS has been updated to load your existing configuration file";
migrated input.css, index.html, postcss.config.js; updated package tailwindcss; installed @tailwindcss/postcss.
Changed files: index.html, package.json, package-lock.json, postcss.config.js, src/input.css.
Template renames: shadow->shadow-sm, shadow-sm->shadow-xs, rounded->rounded-sm, ring->ring-3, outline-none->outline-hidden, flex-grow->grow, bg-gradient-to-r->bg-linear-to-r, blur-sm->blur-xs, overflow-ellipsis->text-ellipsis, !font-bold->font-bold!
input.css: @import 'tailwindcss'; @config '../tailwind.config.js'; a compatibility @layer base block restoring the gray-200 border colour; .btn and .panel became @utility blocks; theme('colors.surface') became var(--color-surface).
Results:
node pix.mjs v3.png up1.png v3.png vs up1.png differing pixels 4279 of 490000
diff of the probe output, v3 against after-the-tool:
b3 border-color: rgb(229,231,235) -> oklch(0.928 0.006 264.531) same colour, new notation
b3 box-shadow: 3px ring rgba(59,130,246,0.5) -> 3px ring in oklch(0.279 0.041 260.031) (the text colour) RING CHANGED
b3 outline: solid 2px -> none 3px outline-none became outline-hidden
b2 background: rgba(0,0,0,0.5) -> rgb(0,0,0) bg-opacity-50 REMOVED
panel bg/radius: rgb(241,245,249) 12px -> rgba(0,0,0,0) 0px var(--color-surface) / var(--radius-card) UNDEFINED
space-x: btn2 margin-left 8px -> btn1 margin-right 8px same layout, different selector
gradient / emerald: same colours, written in oklab/oklch ; transform matrix(1.05) -> none (scale property)
WHY THIS WORKS AS AN ANSWER
---------------------------
The tool is a code rewriter. It handled the mechanical parts (directives, PostCSS plugin, package versions, renamed classes in the right order: shadow->shadow-sm only AFTER shadow-sm->shadow-xs) but said it could not migrate the
JavaScript config, so the colours, radius and font stayed in the old file, and it did not touch bg-opacity-50 or add a ring colour. None of these produced an error or warning: the build exited 0. The only way to see
them is to compare with a baseline, here by computed styles and 4,279 differing pixels. The panel result is the instructive one: the tool converted theme('colors.surface') to var(--color-surface), but the config is still JavaScript and
(as far as I could tell) no such CSS variable exists, so the panel lost its background and radius. I built the baseline carefully first: my first config had a nested theme() call that v3 did not resolve, which I only noticed in the
baseline probe output.