Node.js-specific utilities and runtime functionality for Tailwind CSS v4, providing compilation tools, module dependency analysis, source map handling, path normalization, and optimization utilities.
CSS URL rewriting functionality for asset path management and relative URL transformation in build processes. Handles url() functions and image-set() properties with comprehensive path resolution and rewriting.
Rewrites URLs in CSS for proper relative path handling during build processes.
/**
* Rewrites URLs in CSS for proper relative path handling
* Handles url() and image-set() CSS functions with path transformation
* @param options - Configuration for URL rewriting
* @returns Promise resolving to CSS with rewritten URLs
*/
function rewriteUrls(options: {
/** CSS string containing URLs to rewrite */
css: string;
/** Base directory of the CSS file being processed */
base: string;
/** Root directory for the project (target for relative paths) */
root: string;
}): Promise<string>;Usage Examples:
import { rewriteUrls } from "@tailwindcss/node";
// Basic URL rewriting
const css = `
.hero {
background-image: url('./images/hero.jpg');
}
.icon {
background: url('../assets/icon.svg');
}
`;
const rewritten = await rewriteUrls({
css,
base: '/project/src/styles', // Directory containing the CSS file
root: '/project' // Project root directory
});
console.log(rewritten);
// Output:
// .hero {
// background-image: url('./src/styles/images/hero.jpg');
// }
//
// .icon {
// background: url('./src/assets/icon.svg');
// }Handles CSS image-set() properties with multiple image candidates:
import { rewriteUrls } from "@tailwindcss/node";
const css = `
.responsive-bg {
background-image: image-set(
url('./images/hero-1x.jpg') 1x,
url('./images/hero-2x.jpg') 2x,
url('./images/hero-3x.jpg') 3x
);
}
`;
const rewritten = await rewriteUrls({
css,
base: '/project/src/components',
root: '/project'
});
console.log(rewritten);
// All URLs in the image-set are rewritten relative to the project rootCommon usage in build tools and asset processing:
import { rewriteUrls } from "@tailwindcss/node";
import { compile } from "@tailwindcss/node";
import path from "path";
// Webpack loader example
module.exports = function(source: string) {
const callback = this.async();
const resourcePath = this.resourcePath;
const baseDir = path.dirname(resourcePath);
const rootDir = this.rootContext;
rewriteUrls({
css: source,
base: baseDir,
root: rootDir
}).then(rewrittenCss => {
callback(null, rewrittenCss);
}).catch(callback);
};
// Vite plugin example
export function urlRewritePlugin() {
return {
name: 'url-rewrite',
transform(code: string, id: string) {
if (id.endsWith('.css')) {
return rewriteUrls({
css: code,
base: path.dirname(id),
root: process.cwd()
});
}
}
};
}Use with Tailwind CSS compilation for comprehensive asset handling:
import { compile, rewriteUrls } from "@tailwindcss/node";
async function compileWithUrlRewriting(css: string, options: {
base: string;
shouldRewriteUrls?: boolean;
}) {
// First compile with Tailwind
const compiler = await compile(css, {
...options,
onDependency: (path) => console.log("Dependency:", path),
shouldRewriteUrls: true // This enables automatic URL rewriting
});
const compiled = compiler.build();
// Additional URL rewriting if needed
if (options.shouldRewriteUrls) {
return rewriteUrls({
css: compiled,
base: options.base,
root: process.cwd()
});
}
return compiled;
}
const result = await compileWithUrlRewriting(`
@tailwind base;
.custom {
background: url('./assets/bg.jpg');
}
`, {
base: '/project/src/styles',
shouldRewriteUrls: true
});The URL rewriter processes these CSS functions:
/* Standard url() functions */
.class1 { background: url('./image.jpg'); }
.class2 { background: url("../assets/icon.svg"); }
.class3 { background: url(./fonts/font.woff2); }
/* Image-set functions */
.responsive {
background-image: image-set(
url('./img-1x.jpg') 1x,
url('./img-2x.jpg') 2x
);
}
/* Complex image-set with mixed formats */
.modern {
background-image: image-set(
url('./image.avif') type('image/avif'),
url('./image.webp') type('image/webp'),
url('./image.jpg') type('image/jpeg')
);
}These URL types are not processed:
/* External URLs */
.external { background: url('https://example.com/image.jpg'); }
.protocol { background: url('//cdn.example.com/icon.svg'); }
/* Data URLs */
.data { background: url('data:image/svg+xml;base64,PHN2Zz48L3N2Zz4='); }
/* Absolute paths */
.absolute { background: url('/static/image.jpg'); }
/* CSS functions */
.gradient { background: linear-gradient(to right, red, blue); }
.element { background: element(#myElement); }
/* Dynamic URLs */
.dynamic { background: url(var(--image-url)); }
.function { background: url(DYNAMIC_URL('./path')); }The rewriting process transforms paths as follows:
// Given:
// base: '/project/src/styles'
// root: '/project'
// CSS: url('./images/hero.jpg')
// Step 1: Resolve relative to base
// '/project/src/styles' + './images/hero.jpg' = '/project/src/styles/images/hero.jpg'
// Step 2: Make relative to root
// path.relative('/project', '/project/src/styles/images/hero.jpg') = 'src/styles/images/hero.jpg'
// Step 3: Ensure relative path prefix
// 'src/styles/images/hero.jpg' -> './src/styles/images/hero.jpg'The rewriter preserves and manages quotes appropriately:
/* Input CSS */
.single { background: url('image.jpg'); }
.double { background: url("image.jpg"); }
.none { background: url(image.jpg); }
/* Output CSS (quotes preserved/added as needed) */
.single { background: url('./path/to/image.jpg'); }
.double { background: url("./path/to/image.jpg"); }
.none { background: url(./path/to/image.jpg); }
/* Special characters require quotes */
.spaces { background: url("./path with spaces/image.jpg"); }
.quotes { background: url("./path/image's.jpg"); }import { rewriteUrls } from "@tailwindcss/node";
// Process CSS with custom asset handling
async function processWithAssets(css: string, assetMap: Map<string, string>) {
// First rewrite URLs to normalized paths
const rewritten = await rewriteUrls({
css,
base: './src/styles',
root: './dist'
});
// Then replace with hashed asset paths
let processed = rewritten;
assetMap.forEach((hashedPath, originalPath) => {
processed = processed.replace(
new RegExp(`url\\(['"]?${originalPath}['"]?\\)`, 'g'),
`url('${hashedPath}')`
);
});
return processed;
}
// Usage with asset hashing
const assetMap = new Map([
['./assets/hero.jpg', './assets/hero.abc123.jpg'],
['./fonts/font.woff2', './fonts/font.def456.woff2']
]);
const processed = await processWithAssets(css, assetMap);import { rewriteUrls } from "@tailwindcss/node";
async function processUrls(css: string, isDevelopment: boolean) {
if (isDevelopment) {
// Development: rewrite for dev server
return rewriteUrls({
css,
base: './src/styles',
root: './src'
});
} else {
// Production: rewrite for build output
return rewriteUrls({
css,
base: './src/styles',
root: './dist'
});
}
}import { rewriteUrls } from "@tailwindcss/node";
// Skip processing if no URLs detected
async function optimizedRewrite(css: string, options: Parameters<typeof rewriteUrls>[0]) {
// Quick check to avoid unnecessary processing
if (!css.includes('url(') && !css.includes('image-set(')) {
return css;
}
return rewriteUrls(options);
}
// Cache processed results
const processedCache = new Map<string, string>();
async function cachedRewrite(css: string, options: Parameters<typeof rewriteUrls>[0]) {
const cacheKey = `${css}:${options.base}:${options.root}`;
if (processedCache.has(cacheKey)) {
return processedCache.get(cacheKey)!;
}
const result = await rewriteUrls(options);
processedCache.set(cacheKey, result);
return result;
}The URL rewriter handles errors gracefully:
import { rewriteUrls } from "@tailwindcss/node";
// Malformed CSS is handled gracefully
const malformedCss = `
.broken { background: url('unclosed-quote.jpg; }
.valid { background: url('./valid.jpg'); }
`;
const result = await rewriteUrls({
css: malformedCss,
base: './src',
root: './dist'
});
// Valid URLs are processed, malformed ones are left unchanged
console.log(result);The rewritten CSS maintains compatibility with all modern browsers and build tools that consume CSS with url() and image-set() functions. The output follows standard CSS specifications and works with:
tessl i tessl/npm-tailwindcss--node@4.1.0docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10