/
githubmirror
/
tailwindcss
Обзор
Документация
Войти
/
githubmirror
/
tailwindcss
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/@tailwindcss-browser/src/index.ts
294 строки
7 KB
zhe he
Drop warning from browser build (#18732)
14 авг 2025, 19:20
Не верифицирован
14 авг 2025, 19:20
48f66dc
Код
Авторство
О чём код?
import * as tailwindcss from 'tailwindcss' import * as assets from './assets' import { Instrumentation } from './instrumentation' /** * The type used by `<style>` tags that contain input CSS. */ const STYLE_TYPE = 'text/tailwindcss' /** * The current Tailwind CSS compiler. * * This gets recreated: * - When stylesheets change */ let compiler: Awaited<ReturnType<typeof tailwindcss.compile>> /** * 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<string>() /** * The last input CSS that was compiled. If stylesheets "change" without * actually changing, we can avoid a full rebuild. */ 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() /** * What build this is */ let nextBuildId = 1 /** * Used for instrumenting the build process. This data shows up in the * performance tab of the browser's devtools. */ let I = new Instrumentation() /** * Create the Tailwind CSS compiler * * This handles loading imports, plugins, configs, etc… * * 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() { 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<HTMLStyleElement> = 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 // 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 I.start('Compile CSS') try { compiler = await tailwindcss.compile(css, { base: '/', loadStylesheet, loadModule, }) } finally { I.end('Compile CSS') I.end(`Create compiler`) } classes.clear() } async function loadStylesheet(id: string, base: string) { function load() { if (id === 'tailwindcss') { return { path: 'virtual:tailwindcss/index.css', base, content: assets.css.index, } } else if ( id === 'tailwindcss/preflight' || id === 'tailwindcss/preflight.css' || id === './preflight.css' ) { return { path: 'virtual:tailwindcss/preflight.css', base, content: assets.css.preflight, } } else if ( id === 'tailwindcss/theme' || id === 'tailwindcss/theme.css' || id === './theme.css' ) { return { path: 'virtual:tailwindcss/theme.css', base, content: assets.css.theme, } } else if ( id === 'tailwindcss/utilities' || id === 'tailwindcss/utilities.css' || id === './utilities.css' ) { return { path: 'virtual:tailwindcss/utilities.css', base, content: assets.css.utilities, } } throw new Error(`The browser build does not support @import for "${id}"`) } try { let sheet = load() I.hit(`Loaded stylesheet`, { id, base, size: sheet.content.length, }) return sheet } catch (err) { I.hit(`Failed to load stylesheet`, { id, base, error: (err as Error).message ?? err, }) throw err } } async function loadModule(): Promise<never> { throw new Error(`The browser build does not support plugins or config files.`) } async function build(kind: 'full' | 'incremental') { if (!compiler) return // 1. Refresh the known list of classes let newClasses = new Set<string>() 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, }) if (newClasses.size === 0 && kind === 'incremental') return // 2. Compile the CSS I.start(`Build utilities`) sheet.textContent = compiler.build(Array.from(newClasses)) I.end(`Build utilities`) } function rebuild(kind: 'full' | 'incremental') { async function run() { if (!compiler && kind !== 'full') { return } let buildId = nextBuildId++ I.start(`Build #${buildId} (${kind})`) if (kind === 'full') { await createCompiler() } I.start(`Build`) await build(kind) I.end(`Build`) I.end(`Build #${buildId} (${kind})`) } buildQueue = buildQueue.then(run).catch((err) => I.error(err)) } // 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<HTMLElement>) { 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++ } } if (full > 0) { return rebuild('full') } else if (incremental > 0) { return rebuild('incremental') } }).observe(document.documentElement, { attributes: true, attributeFilter: ['class'], childList: true, subtree: true, }) rebuild('full') document.head.append(sheet)