diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index 409c398e9..5e8ecdbae 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -127,14 +127,14 @@ export async function handle(args: Result>) { if (args['--minify'] || args['--optimize']) { if (css !== previous.css) { DEBUG && I.start('Optimize CSS') - let optimizedCss = optimize(css, { + let optimized = optimize(css, { file: args['--input'] ?? 'input.css', minify: args['--minify'] ?? false, }) DEBUG && I.end('Optimize CSS') previous.css = css - previous.optimizedCss = optimizedCss - output = optimizedCss + previous.optimizedCss = optimized.code + output = optimized.code } else { output = previous.optimizedCss } diff --git a/packages/@tailwindcss-node/package.json b/packages/@tailwindcss-node/package.json index 0844c2324..5fc6727ee 100644 --- a/packages/@tailwindcss-node/package.json +++ b/packages/@tailwindcss-node/package.json @@ -40,6 +40,7 @@ "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "catalog:", + "source-map-js": "^1.2.1", "tailwindcss": "workspace:*" } } diff --git a/packages/@tailwindcss-node/src/index.cts b/packages/@tailwindcss-node/src/index.cts index b9284a0c8..4bca0a5e1 100644 --- a/packages/@tailwindcss-node/src/index.cts +++ b/packages/@tailwindcss-node/src/index.cts @@ -5,6 +5,7 @@ export * from './compile' export * from './instrumentation' export * from './normalize-path' export * from './optimize' +export * from './source-maps' export { env } // In Bun, ESM modules will also populate `require.cache`, so the module hook is diff --git a/packages/@tailwindcss-node/src/index.ts b/packages/@tailwindcss-node/src/index.ts index 83d603429..a5e7bf5b4 100644 --- a/packages/@tailwindcss-node/src/index.ts +++ b/packages/@tailwindcss-node/src/index.ts @@ -5,6 +5,7 @@ export * from './compile' export * from './instrumentation' export * from './normalize-path' export * from './optimize' +export * from './source-maps' export { env } // In Bun, ESM modules will also populate `require.cache`, so the module hook is diff --git a/packages/@tailwindcss-node/src/optimize.ts b/packages/@tailwindcss-node/src/optimize.ts index bf7ab674a..168b3f832 100644 --- a/packages/@tailwindcss-node/src/optimize.ts +++ b/packages/@tailwindcss-node/src/optimize.ts @@ -1,13 +1,29 @@ import { Features, transform } from 'lightningcss' +export interface OptimizeOptions { + /** + * The file being transformed + */ + file?: string + + /** + * Enabled minified output + */ + minify?: boolean +} + +export interface TransformResult { + code: string +} + export function optimize( input: string, - { file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {}, -): string { + { file = 'input.css', minify = false }: OptimizeOptions = {}, +): TransformResult { function optimize(code: Buffer | Uint8Array) { return transform({ filename: file, - code: code as any, + code, minify, sourceMap: false, drafts: { @@ -25,16 +41,18 @@ export function optimize( chrome: 111 << 16, }, errorRecovery: true, - }).code + }) } // Running Lightning CSS twice to ensure that adjacent rules are merged after // nesting is applied. This creates a more optimized output. - let out = optimize(optimize(Buffer.from(input))).toString() + let result = optimize(Buffer.from(input)) + result = optimize(result.code) - // Work around an issue where the media query range syntax transpilation - // generates code that is invalid with `@media` queries level 3. - out = out.replaceAll('@media not (', '@media not all and (') + let code = result.code.toString() + code = code.replaceAll('@media not (', '@media not all and (') - return out + return { + code, + } } diff --git a/packages/@tailwindcss-node/src/source-maps.ts b/packages/@tailwindcss-node/src/source-maps.ts new file mode 100644 index 000000000..88ba54e2f --- /dev/null +++ b/packages/@tailwindcss-node/src/source-maps.ts @@ -0,0 +1,59 @@ +import { SourceMapGenerator } from 'source-map-js' +import type { DecodedSource, DecodedSourceMap } from '../../tailwindcss/src/source-maps/source-map' +import { DefaultMap } from '../../tailwindcss/src/utils/default-map' + +export type { DecodedSource, DecodedSourceMap } +export interface SourceMap { + readonly raw: string + readonly inline: string +} + +function serializeSourceMap(map: DecodedSourceMap): string { + let generator = new SourceMapGenerator() + + let id = 1 + let sourceTable = new DefaultMap< + DecodedSource | null, + { + url: string + content: string + } + >((src) => { + return { + url: src?.url ?? ``, + content: src?.content ?? '', + } + }) + + for (let mapping of map.mappings) { + let original = sourceTable.get(mapping.originalPosition?.source ?? null) + + generator.addMapping({ + generated: mapping.generatedPosition, + original: mapping.originalPosition, + source: original.url, + name: mapping.name, + }) + + generator.setSourceContent(original.url, original.content) + } + + return generator.toString() +} + +export function toSourceMap(map: DecodedSourceMap | string): SourceMap { + let raw = typeof map === 'string' ? map : serializeSourceMap(map) + + return { + raw, + get inline() { + let tmp = '' + + tmp += '/*# sourceMappingURL=data:application/json;base64,' + tmp += Buffer.from(raw, 'utf-8').toString('base64') + tmp += ' */\n' + + return tmp + }, + } +} diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts index b06c15cb2..79e51d2aa 100644 --- a/packages/@tailwindcss-postcss/src/index.ts +++ b/packages/@tailwindcss-postcss/src/index.ts @@ -282,13 +282,13 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { DEBUG && I.end('AST -> CSS') DEBUG && I.start('Lightning CSS') - let ast = optimizeCss(css, { + let optimized = optimizeCss(css, { minify: typeof optimize === 'object' ? optimize.minify : true, }) DEBUG && I.end('Lightning CSS') DEBUG && I.start('CSS -> PostCSS AST') - context.optimizedPostCssAst = postcss.parse(ast, result.opts) + context.optimizedPostCssAst = postcss.parse(optimized.code, result.opts) DEBUG && I.end('CSS -> PostCSS AST') DEBUG && I.end('Optimization') diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index a62929069..0a053291a 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -106,7 +106,9 @@ export default function tailwindcss(): Plugin[] { DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)') DEBUG && I.start('[@tailwindcss/vite] Optimize CSS') - result.code = optimize(result.code, { minify }) + result = optimize(result.code, { + minify, + }) DEBUG && I.end('[@tailwindcss/vite] Optimize CSS') return result diff --git a/packages/tailwindcss/src/test-utils/run.ts b/packages/tailwindcss/src/test-utils/run.ts index 3ecf1a10f..5b4b1ca99 100644 --- a/packages/tailwindcss/src/test-utils/run.ts +++ b/packages/tailwindcss/src/test-utils/run.ts @@ -7,12 +7,14 @@ export async function compileCss( options: Parameters[1] = {}, ) { let { build } = await compile(css, options) - return optimize(build(candidates)).trim() + return optimize(build(candidates)).code.trim() } export async function run(candidates: string[]) { let { build } = await compile('@tailwind utilities;') - return optimize(build(candidates)).trim() + return optimize(build(candidates)).code.trim() } -export const optimizeCss = optimize +export function optimizeCss(input: string) { + return optimize(input).code +}