From d35cf46fb5f274e8d4366ded95ea70220afa4127 Mon Sep 17 00:00:00 2001 From: ssz Date: Sat, 17 May 2025 17:29:04 +0200 Subject: [PATCH 1/3] change the tailwind browser and make it importable --- packages/@tailwindcss-browser/package.json | 13 +- packages/@tailwindcss-browser/src/index.ts | 119 ++++--------------- packages/@tailwindcss-browser/tsconfig.json | 9 ++ packages/@tailwindcss-browser/tsup.config.ts | 4 +- 4 files changed, 40 insertions(+), 105 deletions(-) diff --git a/packages/@tailwindcss-browser/package.json b/packages/@tailwindcss-browser/package.json index 700cad815c0c..20ce420b82f7 100644 --- a/packages/@tailwindcss-browser/package.json +++ b/packages/@tailwindcss-browser/package.json @@ -18,10 +18,6 @@ "dev": "pnpm run build -- --watch", "test:ui": "playwright test" }, - "exports": { - ".": "./dist/index.global.js", - "./package.json": "./package.json" - }, "files": [ "dist" ], @@ -33,5 +29,12 @@ "h3": "^1.15.3", "listhen": "^1.9.0", "tailwindcss": "workspace:*" + }, + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs" + }, + "./package.json": "./package.json" } -} +} \ No newline at end of file diff --git a/packages/@tailwindcss-browser/src/index.ts b/packages/@tailwindcss-browser/src/index.ts index 74992157f5d5..3b5a771c9aa0 100644 --- a/packages/@tailwindcss-browser/src/index.ts +++ b/packages/@tailwindcss-browser/src/index.ts @@ -8,11 +8,6 @@ console.warn( 'The browser build of Tailwind CSS should not be used in production. To use Tailwind CSS in production, use the Tailwind CLI, Vite plugin, or PostCSS plugin: https://tailwindcss.com/docs/installation', ) -/** - * The type used by `` tags that contain input CSS. - */ -const STYLE_TYPE = 'text/tailwindcss' - /** * The current Tailwind CSS compiler. * @@ -33,16 +28,12 @@ let classes = new Set() */ let lastCss = '' -/** - * The stylesheet that we use to inject the compiled CSS into the page. - */ -let sheet = document.createElement('style') /** * The queue of build tasks that need to be run. This is used to ensure that we * don't run multiple builds concurrently. */ -let buildQueue = Promise.resolve() +let buildQueue = Promise.resolve('') /** * What build this is @@ -63,21 +54,10 @@ let I = new Instrumentation() * This does **not** imply that the CSS is actually built. That happens in the * `build` function and is a separate scheduled task. */ -async function createCompiler() { +async function createCompiler(css: string) { I.start(`Create compiler`) I.start('Reading Stylesheets') - // The stylesheets may have changed causing a full rebuild so we'll need to - // gather the latest list of stylesheets. - let stylesheets: Iterable = document.querySelectorAll( - `style[type="${STYLE_TYPE}"]`, - ) - - let css = '' - for (let sheet of stylesheets) { - observeSheet(sheet) - css += sheet.textContent + '\n' - } // The user might have no stylesheets, or a some stylesheets without `@import` // because they want to customize their theme so we'll inject the main import @@ -180,7 +160,7 @@ async function loadModule(): Promise { throw new Error(`The browser build does not support plugins or config files.`) } -async function build(kind: 'full' | 'incremental') { +async function build() { if (!compiler) return // 1. Refresh the known list of classes @@ -201,100 +181,41 @@ async function build(kind: 'full' | 'incremental') { count: newClasses.size, }) - if (newClasses.size === 0 && kind === 'incremental') return - // 2. Compile the CSS I.start(`Build utilities`) - sheet.textContent = compiler.build(Array.from(newClasses)) + const result = compiler.build(Array.from(newClasses)) I.end(`Build utilities`) + + return result; } -function rebuild(kind: 'full' | 'incremental') { +async function rebuild(css: string) { async function run() { - if (!compiler && kind !== 'full') { - return - } let buildId = nextBuildId++ - I.start(`Build #${buildId} (${kind})`) + I.start(`Build #${buildId}`) + + await createCompiler(css) - if (kind === 'full') { - await createCompiler() - } I.start(`Build`) - await build(kind) + const result = await build(); I.end(`Build`) - I.end(`Build #${buildId} (${kind})`) - } - - buildQueue = buildQueue.then(run).catch((err) => I.error(err)) -} + I.end(`Build #${buildId}`) -// Handle changes to known stylesheets -let styleObserver = new MutationObserver(() => rebuild('full')) - -function observeSheet(sheet: HTMLStyleElement) { - styleObserver.observe(sheet, { - attributes: true, - attributeFilter: ['type'], - characterData: true, - subtree: true, - childList: true, - }) -} - -// Handle changes to the document that could affect the styles -// - Changes to any element's class attribute -// - New stylesheets being added to the page -// - New elements (with classes) being added to the page -new MutationObserver((records) => { - let full = 0 - let incremental = 0 - - for (let record of records) { - // New stylesheets == tracking + full rebuild - for (let node of record.addedNodes as Iterable) { - if (node.nodeType !== Node.ELEMENT_NODE) continue - if (node.tagName !== 'STYLE') continue - if (node.getAttribute('type') !== STYLE_TYPE) continue - - observeSheet(node as HTMLStyleElement) - full++ - } - - // New nodes require an incremental rebuild - for (let node of record.addedNodes) { - if (node.nodeType !== 1) continue - - // Skip the output stylesheet itself to prevent loops - if (node === sheet) continue - - incremental++ - } - - // Changes to class attributes require an incremental rebuild - if (record.type === 'attributes') { - incremental++ - } + return result ?? ''; } - if (full > 0) { - return rebuild('full') - } else if (incremental > 0) { - return rebuild('incremental') + try { + buildQueue = buildQueue.then(run); + return await buildQueue; + } catch (error) { + I.error(error); } -}).observe(document.documentElement, { - attributes: true, - attributeFilter: ['class'], - childList: true, - subtree: true, -}) - -rebuild('full') +} +export const tailwindCompiler = rebuild; -document.head.append(sheet) diff --git a/packages/@tailwindcss-browser/tsconfig.json b/packages/@tailwindcss-browser/tsconfig.json index 8ea040daf8d3..b9e1ccb07257 100644 --- a/packages/@tailwindcss-browser/tsconfig.json +++ b/packages/@tailwindcss-browser/tsconfig.json @@ -2,5 +2,14 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "lib": ["es2022", "esnext.disposable", "dom", "dom.iterable"], + "target": "ESNext", + "module": "ESNext", + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, }, + "include": ["src"] } diff --git a/packages/@tailwindcss-browser/tsup.config.ts b/packages/@tailwindcss-browser/tsup.config.ts index bb0ddc39cde4..654a9c0db5bc 100644 --- a/packages/@tailwindcss-browser/tsup.config.ts +++ b/packages/@tailwindcss-browser/tsup.config.ts @@ -1,9 +1,11 @@ import { defineConfig } from 'tsup' export default defineConfig({ - format: ['iife'], + format: ['esm', 'cjs'], clean: true, minify: true, + sourcemap: true, + dts: true, entry: ['src/index.ts'], noExternal: [/.*/], loader: { From 10e683461bee99e8deeca6cbd2749bd20017a155 Mon Sep 17 00:00:00 2001 From: ssz Date: Sat, 17 May 2025 17:34:19 +0200 Subject: [PATCH 2/3] add description --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 95ec9d87ddcc..d713e64827fe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ +## Tailwind in browser +changed the packages/@tailwindcss-browser project to use it as a library + +- clone it +- add it in the package.js dependencies + + `"@tailwindcss-browser": "file:../../tailwindcss-browser/packages/@tailwindcss-browser"` +- install + + `pnpm install` +- import + + `import { tailwindCompiler } from "@tailwindcss-browser";` + + +---

From d7b996dfbbb3e4162628855f80f384fb41fed9a1 Mon Sep 17 00:00:00 2001 From: ssz Date: Sat, 17 May 2025 20:18:21 +0200 Subject: [PATCH 3/3] simplify --- packages/@tailwindcss-browser/src/index.ts | 94 +++------------------- 1 file changed, 9 insertions(+), 85 deletions(-) diff --git a/packages/@tailwindcss-browser/src/index.ts b/packages/@tailwindcss-browser/src/index.ts index 3b5a771c9aa0..290f435dc4df 100644 --- a/packages/@tailwindcss-browser/src/index.ts +++ b/packages/@tailwindcss-browser/src/index.ts @@ -16,29 +16,6 @@ console.warn( */ let compiler: Awaited> -/** - * The list of all seen classes on the page so far. The compiler already has a - * cache of classes but this lets us only pass new classes to `build(…)`. - */ -let classes = new Set() - -/** - * The last input CSS that was compiled. If stylesheets "change" without - * actually changing, we can avoid a full rebuild. - */ -let lastCss = '' - - -/** - * The queue of build tasks that need to be run. This is used to ensure that we - * don't run multiple builds concurrently. - */ -let buildQueue = Promise.resolve('') - -/** - * What build this is - */ -let nextBuildId = 1 /** * Used for instrumenting the build process. This data shows up in the @@ -54,28 +31,11 @@ let I = new Instrumentation() * This does **not** imply that the CSS is actually built. That happens in the * `build` function and is a separate scheduled task. */ -async function createCompiler(css: string) { +async function createCompiler() { I.start(`Create compiler`) I.start('Reading Stylesheets') - - // The user might have no stylesheets, or a some stylesheets without `@import` - // because they want to customize their theme so we'll inject the main import - // for them. However, if they start using `@import` we'll let them control - // the build completely. - if (!css.includes('@import')) { - css = `@import "tailwindcss";${css}` - } - - I.end('Reading Stylesheets', { - size: css.length, - changed: lastCss !== css, - }) - - // The input CSS did not change so the compiler does not need to be recreated - if (lastCss === css) return - - lastCss = css + const css = `@import "tailwindcss";` I.start('Compile CSS') try { @@ -88,8 +48,6 @@ async function createCompiler(css: string) { I.end('Compile CSS') I.end(`Create compiler`) } - - classes.clear() } async function loadStylesheet(id: string, base: string) { @@ -160,59 +118,25 @@ async function loadModule(): Promise { throw new Error(`The browser build does not support plugins or config files.`) } -async function build() { - if (!compiler) return - // 1. Refresh the known list of classes - let newClasses = new Set() - - I.start(`Collect classes`) - - for (let element of document.querySelectorAll('[class]')) { - for (let c of element.classList) { - if (classes.has(c)) continue - - classes.add(c) - newClasses.add(c) - } - } - - I.end(`Collect classes`, { - count: newClasses.size, - }) - - // 2. Compile the CSS - I.start(`Build utilities`) - - const result = compiler.build(Array.from(newClasses)) - - I.end(`Build utilities`) - - return result; -} - -async function rebuild(css: string) { +async function rebuild(classes: Set) { async function run() { - let buildId = nextBuildId++ - - I.start(`Build #${buildId}`) - - await createCompiler(css) - + if (!compiler) { + await createCompiler() + } I.start(`Build`) - const result = await build(); + const result = compiler.build(Array.from(classes)) I.end(`Build`) - I.end(`Build #${buildId}`) + I.end(`Build`) return result ?? ''; } try { - buildQueue = buildQueue.then(run); - return await buildQueue; + return await Promise.resolve().then(run);; } catch (error) { I.error(error); }