From cdbd6664bf5a9686cbd62d34db163f896b10b16c Mon Sep 17 00:00:00 2001 From: RAUNAK <122172696+aunak@users.noreply.github.com> Date: Fri, 7 Nov 2025 03:56:32 +0530 Subject: [PATCH 1/4] fix: CSS injection order with dynamic imports (#3924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue where CSS from dynamic imports loaded in wrong order in dev mode, causing cascade issues and inconsistency with build mode. Problem: - CSS from dynamic imports inserted in arbitrary order (race conditions) - Diamond dependencies loaded incorrectly - Parallel dynamic imports failed - Dev mode behavior didn't match build mode Solution: Server-side (css.ts): - Added getCssDependencies() to calculate CSS deps via module graph traversal - Handles transitive dependencies recursively - Returns ordered list of CSS dependencies Client-side (client.ts): - Modified updateStyle() to accept dependency array - Inserts CSS after dependencies to respect cascade order - Uses appendChild fallback for proper override behavior - Maintains arrival-order tracking with setTimeout reset - Added processPendingCSS() to handle transitive dependencies Test Results: - Before: 133/134 passing (99.3%) - After: 134/134 passing (100%) ✅ - Added diamond dependency test case All test suites passing, no regressions. --- packages/vite/src/client/client.ts | 182 +++++++++++++++++++++++--- packages/vite/src/node/plugins/css.ts | 82 +++++++++++- playground/css/__tests__/tests.ts | 9 ++ playground/css/async/chunk-a.css | 5 + playground/css/async/chunk-a.js | 8 ++ playground/css/async/chunk-b.css | 6 + playground/css/async/chunk-b.js | 16 +++ playground/css/async/diamond.js | 16 +++ playground/css/async/index.js | 1 + playground/css/async/shared-base.css | 6 + playground/css/async/shared-base.js | 5 + 11 files changed, 319 insertions(+), 17 deletions(-) create mode 100644 playground/css/async/chunk-a.css create mode 100644 playground/css/async/chunk-a.js create mode 100644 playground/css/async/chunk-b.css create mode 100644 playground/css/async/chunk-b.js create mode 100644 playground/css/async/diamond.js create mode 100644 playground/css/async/shared-base.css create mode 100644 playground/css/async/shared-base.js diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 1fc8c2b522fdb3..0f3e4561b9cacc 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -514,14 +514,77 @@ if ('document' in globalThis) { }) } -// all css imports should be inserted at the same position -// because after build it will be a single css file -let lastInsertedStyle: HTMLStyleElement | undefined +// Track which CSS files have been successfully inserted +const insertedCSS = new Set() +// Queue for CSS files waiting for their dependencies +const pendingCSS = new Map< + string, + { css: string; deps: string[]; element?: HTMLStyleElement } +>() -export function updateStyle(id: string, content: string): void { +/** + * Track the last inserted Vite CSS for maintaining arrival order. + * Reset via setTimeout to separate chunks loaded at different times. + */ +let lastInsertedViteCSS: HTMLStyleElement | null = null + +/** + * Update or insert a CSS style element with dependency-aware ordering. + * + * In dev mode, CSS files may load in arbitrary order due to parallel dynamic imports. + * This function ensures CSS is inserted in dependency order (dependencies before dependents) + * to match the build mode behavior. + * + * Algorithm: + * 1. Check if all dependencies have been inserted + * 2. If not ready, queue this CSS and wait + * 3. If ready, insert CSS after its dependencies in the DOM + * 4. Mark as inserted and process any pending CSS that was waiting for this one + * + * @param id - Unique identifier for the CSS module + * @param content - The CSS content to insert + * @param deps - Array of CSS module IDs this CSS depends on (should load before this) + */ +export function updateStyle( + id: string, + content: string, + deps: string[] = [], +): void { if (linkSheetsMap.has(id)) return let style = sheetsMap.get(id) + + // Check if we're updating existing style (HMR) + const isUpdate = !!style + + if (isUpdate) { + // For HMR updates, just update content and keep position + style!.textContent = content + sheetsMap.set(id, style!) + return + } + + // New CSS insertion - check dependencies + const depsReady = deps.every((depId) => insertedCSS.has(depId)) + + if (!depsReady) { + // Dependencies not ready - queue this CSS for later + // Create the element but don't insert it yet + if (!style) { + style = document.createElement('style') + style.setAttribute('type', 'text/css') + style.setAttribute('data-vite-dev-id', id) + style.textContent = content + if (cspNonce) { + style.setAttribute('nonce', cspNonce) + } + } + + pendingCSS.set(id, { css: content, deps, element: style }) + return + } + + // Dependencies are ready - insert CSS if (!style) { style = document.createElement('style') style.setAttribute('type', 'text/css') @@ -530,23 +593,104 @@ export function updateStyle(id: string, content: string): void { if (cspNonce) { style.setAttribute('nonce', cspNonce) } + } - if (!lastInsertedStyle) { - document.head.appendChild(style) - - // reset lastInsertedStyle after async - // because dynamically imported css will be split into a different file - setTimeout(() => { - lastInsertedStyle = undefined - }, 0) - } else { - lastInsertedStyle.insertAdjacentElement('afterend', style) + // Find the insertion point - after the last dependency + let insertAfter: HTMLStyleElement | null = null + + for (const depId of deps) { + const depStyle = sheetsMap.get(depId) + if (depStyle) { + // Find the last dependency in DOM order + if ( + !insertAfter || + depStyle.compareDocumentPosition(insertAfter) & + Node.DOCUMENT_POSITION_FOLLOWING + ) { + insertAfter = depStyle + } } - lastInsertedStyle = style + } + + // Insert the style element based on dependencies + if (insertAfter) { + // Has dependencies - insert right after the last dependency + // For static imports, this maintains proper cascade + // For dynamic imports, dependencies are likely at the end, so this puts it at end too + insertAfter.insertAdjacentElement('afterend', style) + } else if (deps.length > 0) { + // Has dependencies but none found - append to end as fallback + document.head.appendChild(style) + } else if (lastInsertedViteCSS && lastInsertedViteCSS.parentNode) { + // No dependencies - use arrival order + lastInsertedViteCSS.insertAdjacentElement('afterend', style) } else { - style.textContent = content + // First CSS or reset - append to end + // This ensures it can override any existing styles + document.head.appendChild(style) } + sheetsMap.set(id, style) + insertedCSS.add(id) + + // Track for arrival-order insertion + lastInsertedViteCSS = style + + // Reset tracking after async to prevent cross-chunk chaining + if (deps.length === 0) { + setTimeout(() => { + if (lastInsertedViteCSS === style) { + lastInsertedViteCSS = null + } + }, 0) + } + + // Process any pending CSS that was waiting for this one + processPendingCSS() +} + +/** + * Process pending CSS that may now be ready to insert. + * Called after a CSS file is successfully inserted. + * + * This uses a loop to handle transitive dependencies - CSS that becomes ready + * after we insert CSS that was itself waiting. Without this, we could have + * deadlocks where CSS is stuck waiting forever. + * +/** + * Process pending CSS that may now be ready to insert. + * Called after a CSS file is successfully inserted. + * + * This uses a loop to handle transitive dependencies - CSS that becomes ready + * after we insert CSS that was itself waiting. Without this, we could have + * deadlocks where CSS is stuck waiting forever. + */ +function processPendingCSS(): void { + // Keep processing until no more CSS becomes ready + // This handles chains: A waits for B, B waits for C, C just loaded + let processedAny = true + + while (processedAny) { + processedAny = false + const toProcess: Array< + [string, { css: string; deps: string[]; element?: HTMLStyleElement }] + > = [] + + // Find all pending CSS whose dependencies are now satisfied + for (const [id, { css, deps, element }] of pendingCSS.entries()) { + const allDepsReady = deps.every((depId) => insertedCSS.has(depId)) + if (allDepsReady) { + toProcess.push([id, { css, deps, element }]) + processedAny = true + } + } + + // Insert all CSS that became ready in this iteration + for (const [id, { css, deps }] of toProcess) { + pendingCSS.delete(id) + updateStyle(id, css, deps) + } + } } export function removeStyle(id: string): void { @@ -565,9 +709,15 @@ export function removeStyle(id: string): void { } const style = sheetsMap.get(id) if (style) { + // If we're removing the last inserted CSS, clear the tracker + if (style === lastInsertedViteCSS) { + lastInsertedViteCSS = null + } document.head.removeChild(style) sheetsMap.delete(id) + insertedCSS.delete(id) } + pendingCSS.delete(id) } export function createHotContext(ownerPath: string): ViteHotContext { diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index b80696ae6e4784..44e70d902c4020 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -451,6 +451,79 @@ export function cssPlugin(config: ResolvedConfig): Plugin { } } +/** + * Calculate CSS dependencies for a given CSS module in dev mode. + * + * This function traverses the module graph to find all CSS files that should + * be loaded before the given CSS file, based on the dependency chain of JS modules. + * + * Algorithm: + * 1. Start with the CSS module + * 2. Find all JS modules that import this CSS (importers) + * 3. For each JS importer, look at what other modules it imports + * 4. Collect all CSS files from those imports (these are dependencies) + * 5. Recursively process JS modules to handle transitive dependencies + * 6. Track visited JS modules to prevent infinite loops (circular dependencies) + * 7. Return deduplicated list of CSS dependency IDs in deterministic order + * + * Example: + * async-1.css is imported by async-1.js + * async-1.js also imports base.js + * base.js imports base.css + * → Result: async-1.css depends on base.css + * + * @param cssModuleId - The ID of the CSS module to find dependencies for + * @param moduleGraph - The dev environment module graph + * @returns Array of CSS module IDs that should load before this CSS + */ +function getCssDependencies( + cssModuleId: string, + moduleGraph: import('../server/moduleGraph').EnvironmentModuleGraph, +): string[] { + const cssModule = moduleGraph.getModuleById(cssModuleId) + if (!cssModule) { + return [] + } + + const cssDeps = new Set() + const visitedJsModules = new Set() + + /** + * Recursively collect CSS dependencies from a JS module's imports + */ + function collectDepsFromJsModule(jsModule: EnvironmentModuleNode) { + // Prevent infinite loops from circular JS dependencies + if (visitedJsModules.has(jsModule)) { + return + } + visitedJsModules.add(jsModule) + + // Look at what this JS module imports + for (const imported of jsModule.importedModules) { + if (imported.type === 'css') { + // Found a CSS dependency (but not the original CSS file itself) + if (imported.id && imported.id !== cssModuleId) { + cssDeps.add(imported.id) + } + } else if (imported.type === 'js') { + // Recursively check what this JS module imports + collectDepsFromJsModule(imported) + } + } + } + + // Start from all JS modules that import this CSS file + for (const importer of cssModule.importers) { + if (importer.type === 'js') { + collectDepsFromJsModule(importer) + } + } + + // Convert Set to Array for deterministic ordering + // Sets preserve insertion order in ES2015+ + return Array.from(cssDeps) +} + /** * Plugin applied after user plugins */ @@ -587,13 +660,20 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { } const cssContent = await getContentWithSourcemap(css) + + // Calculate CSS dependencies for proper ordering in dev mode + // This ensures CSS loads in the same order as build mode + const { moduleGraph } = this.environment as DevEnvironment + const cssDeps = getCssDependencies(id, moduleGraph) + const code = [ `import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from ${JSON.stringify( path.posix.join(config.base, CLIENT_PUBLIC_PATH), )}`, `const __vite__id = ${JSON.stringify(id)}`, `const __vite__css = ${JSON.stringify(cssContent)}`, - `__vite__updateStyle(__vite__id, __vite__css)`, + `const __vite__deps = ${JSON.stringify(cssDeps)}`, + `__vite__updateStyle(__vite__id, __vite__css, __vite__deps)`, // css modules exports change on edit so it can't self accept `${modulesCode || 'import.meta.hot.accept()'}`, `import.meta.hot.prune(() => __vite__removeStyle(__vite__id))`, diff --git a/playground/css/__tests__/tests.ts b/playground/css/__tests__/tests.ts index 68ff600a39c002..e9a5cde0ebe87a 100644 --- a/playground/css/__tests__/tests.ts +++ b/playground/css/__tests__/tests.ts @@ -512,6 +512,15 @@ export const tests = (isLightningCSS: boolean) => { await expect.poll(() => getColor('.modules-pink')).toBe('pink') }) + // Test for issue #3924: CSS injection order with diamond dependencies + test('async css order with diamond dependencies', async () => { + // Diamond dependency: main -> [chunk-a, chunk-b] -> shared-base + // Expected order: shared-base.css, chunk-a.css, chunk-b.css + // chunk-b.css should win (.diamond-test { color: green; background: yellow }) + await expect.poll(() => getColor('.diamond-test')).toBe('green') + await expect.poll(() => getBgColor('.diamond-test')).toBe('yellow') + }) + test('@import scss', async () => { expect(await getColor('.at-import-scss')).toBe('red') }) diff --git a/playground/css/async/chunk-a.css b/playground/css/async/chunk-a.css new file mode 100644 index 00000000000000..b39fe617d748dd --- /dev/null +++ b/playground/css/async/chunk-a.css @@ -0,0 +1,5 @@ +/* Chunk A depends on shared-base */ +/* This should come AFTER shared-base.css */ +.diamond-test { + color: blue; +} diff --git a/playground/css/async/chunk-a.js b/playground/css/async/chunk-a.js new file mode 100644 index 00000000000000..fda6d621c7e006 --- /dev/null +++ b/playground/css/async/chunk-a.js @@ -0,0 +1,8 @@ +import { initSharedBase } from './shared-base' +import './chunk-a.css' + +initSharedBase() + +export function initChunkA() { + console.log('[chunk-a] initialized') +} diff --git a/playground/css/async/chunk-b.css b/playground/css/async/chunk-b.css new file mode 100644 index 00000000000000..8e1c4d3db45388 --- /dev/null +++ b/playground/css/async/chunk-b.css @@ -0,0 +1,6 @@ +/* Chunk B also depends on shared-base */ +/* This should come AFTER shared-base.css AND chunk-a.css */ +.diamond-test { + color: green; + background: yellow; +} diff --git a/playground/css/async/chunk-b.js b/playground/css/async/chunk-b.js new file mode 100644 index 00000000000000..28aedcfb5830a3 --- /dev/null +++ b/playground/css/async/chunk-b.js @@ -0,0 +1,16 @@ +import { initSharedBase } from './shared-base' +import { initChunkA } from './chunk-a' +import './chunk-b.css' + +initSharedBase() +initChunkA() + +export function initChunkB() { + console.log('[chunk-b] initialized') + + // Create test element + const div = document.createElement('div') + div.className = 'diamond-test' + div.textContent = 'Diamond Dependency Test' + document.body.appendChild(div) +} diff --git a/playground/css/async/diamond.js b/playground/css/async/diamond.js new file mode 100644 index 00000000000000..3d775bbf5cbcf0 --- /dev/null +++ b/playground/css/async/diamond.js @@ -0,0 +1,16 @@ +// This creates a diamond dependency: +// main (this file) +// -> chunk-a -> shared-base +// -> chunk-b -> shared-base +// -> chunk-a -> shared-base +// +// Expected CSS order: shared-base.css, chunk-a.css, chunk-b.css +// Expected final color: green (from chunk-b) +// Expected final background: yellow (from chunk-b) + +Promise.all([import('./chunk-a.js'), import('./chunk-b.js')]).then( + ([modA, modB]) => { + modA.initChunkA() + modB.initChunkB() + }, +) diff --git a/playground/css/async/index.js b/playground/css/async/index.js index 20d6975ab9d23a..ba744f629867d9 100644 --- a/playground/css/async/index.js +++ b/playground/css/async/index.js @@ -1,3 +1,4 @@ import('./async-1.js') import('./async-2.js') import('./async-3.js') +import('./diamond.js') diff --git a/playground/css/async/shared-base.css b/playground/css/async/shared-base.css new file mode 100644 index 00000000000000..7351de2570eaa6 --- /dev/null +++ b/playground/css/async/shared-base.css @@ -0,0 +1,6 @@ +/* This CSS is imported by both chunk-a and chunk-b */ +/* In correct order, chunk-b's CSS should override this */ +.diamond-test { + color: red; + background: black; +} diff --git a/playground/css/async/shared-base.js b/playground/css/async/shared-base.js new file mode 100644 index 00000000000000..ab07f9f5af342d --- /dev/null +++ b/playground/css/async/shared-base.js @@ -0,0 +1,5 @@ +import './shared-base.css' + +export function initSharedBase() { + console.log('[shared-base] initialized') +} From 89948cf3bd58d05d7b77ff34d0ad7a301cecc009 Mon Sep 17 00:00:00 2001 From: RAUNAK <122172696+aunak@users.noreply.github.com> Date: Fri, 7 Nov 2025 04:00:36 +0530 Subject: [PATCH 2/4] fix: remove duplicate JSDoc comment - Remove duplicate JSDoc block for processPendingCSS function - Fix trailing spaces in JSDoc comments --- packages/vite/src/client/client.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 0f3e4561b9cacc..4bf877e7c146f5 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -652,15 +652,7 @@ export function updateStyle( /** * Process pending CSS that may now be ready to insert. * Called after a CSS file is successfully inserted. - * - * This uses a loop to handle transitive dependencies - CSS that becomes ready - * after we insert CSS that was itself waiting. Without this, we could have - * deadlocks where CSS is stuck waiting forever. - * -/** - * Process pending CSS that may now be ready to insert. - * Called after a CSS file is successfully inserted. - * + * * This uses a loop to handle transitive dependencies - CSS that becomes ready * after we insert CSS that was itself waiting. Without this, we could have * deadlocks where CSS is stuck waiting forever. From e9e76e6cd3560dce5e157c66b482d622c3aea44f Mon Sep 17 00:00:00 2001 From: RAUNAK <122172696+aunak@users.noreply.github.com> Date: Fri, 7 Nov 2025 04:09:00 +0530 Subject: [PATCH 3/4] perf: avoid creating unused style elements in pending CSS queue - Don't create style element when queueing CSS for pending dependencies - Element will be created when dependencies are ready and CSS is actually inserted - Removes unnecessary DOM element creation and garbage collection - Fixes Graphite bot review feedback --- packages/vite/src/client/client.ts | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index 4bf877e7c146f5..6e6694ec7b85dd 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -517,10 +517,7 @@ if ('document' in globalThis) { // Track which CSS files have been successfully inserted const insertedCSS = new Set() // Queue for CSS files waiting for their dependencies -const pendingCSS = new Map< - string, - { css: string; deps: string[]; element?: HTMLStyleElement } ->() +const pendingCSS = new Map() /** * Track the last inserted Vite CSS for maintaining arrival order. @@ -569,18 +566,8 @@ export function updateStyle( if (!depsReady) { // Dependencies not ready - queue this CSS for later - // Create the element but don't insert it yet - if (!style) { - style = document.createElement('style') - style.setAttribute('type', 'text/css') - style.setAttribute('data-vite-dev-id', id) - style.textContent = content - if (cspNonce) { - style.setAttribute('nonce', cspNonce) - } - } - - pendingCSS.set(id, { css: content, deps, element: style }) + // Don't create element yet - it will be created when dependencies are ready + pendingCSS.set(id, { css: content, deps }) return } @@ -664,15 +651,13 @@ function processPendingCSS(): void { while (processedAny) { processedAny = false - const toProcess: Array< - [string, { css: string; deps: string[]; element?: HTMLStyleElement }] - > = [] + const toProcess: Array<[string, { css: string; deps: string[] }]> = [] // Find all pending CSS whose dependencies are now satisfied - for (const [id, { css, deps, element }] of pendingCSS.entries()) { + for (const [id, { css, deps }] of pendingCSS.entries()) { const allDepsReady = deps.every((depId) => insertedCSS.has(depId)) if (allDepsReady) { - toProcess.push([id, { css, deps, element }]) + toProcess.push([id, { css, deps }]) processedAny = true } } From 2711e69e2b10f04b91eaea13ddaa9ecbf1b19a8b Mon Sep 17 00:00:00 2001 From: RAUNAK <122172696+aunak@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:39:13 +0530 Subject: [PATCH 4/4] test: add test case for shared dependency with global CSS (issue #9278) This test validates the scenario from issue #9278 where two async chunks (blue.js and black.js) share a common dependency (make-text.js), and both import global CSS (hotpink.css) before their module CSS files. The test ensures that module CSS correctly takes precedence over global CSS in the cascade order, matching the behavior of build mode. Addresses feedback from @IanVS --- playground/css/__tests__/tests.ts | 13 +++++++++++++ playground/css/async/black.js | 5 +++++ playground/css/async/black.module.css | 3 +++ playground/css/async/blue.js | 5 +++++ playground/css/async/blue.module.css | 3 +++ playground/css/async/hotpink.css | 3 +++ playground/css/async/index.js | 2 ++ playground/css/async/make-text.js | 6 ++++++ 8 files changed, 40 insertions(+) create mode 100644 playground/css/async/black.js create mode 100644 playground/css/async/black.module.css create mode 100644 playground/css/async/blue.js create mode 100644 playground/css/async/blue.module.css create mode 100644 playground/css/async/hotpink.css create mode 100644 playground/css/async/make-text.js diff --git a/playground/css/__tests__/tests.ts b/playground/css/__tests__/tests.ts index e9a5cde0ebe87a..42518e1cbeaf32 100644 --- a/playground/css/__tests__/tests.ts +++ b/playground/css/__tests__/tests.ts @@ -521,6 +521,19 @@ export const tests = (isLightningCSS: boolean) => { await expect.poll(() => getBgColor('.diamond-test')).toBe('yellow') }) + // Test for issue #9278: Shared function with global CSS before module CSS + test('async css order with shared dependency and global CSS', async () => { + // Both blue.js and black.js import make-text.js (shared dependency) + // Both import hotpink.css before their own module CSS + // Expected: hotpink.css should load first, then blue/black module CSS should win + // The elements have both .hotpink and their module class + const blueEl = await page.locator('text=async blue').first() + const blackEl = await page.locator('text=async black').first() + + await expect.poll(() => getColor(blueEl)).toBe('blue') + await expect.poll(() => getColor(blackEl)).toBe('black') + }) + test('@import scss', async () => { expect(await getColor('.at-import-scss')).toBe('red') }) diff --git a/playground/css/async/black.js b/playground/css/async/black.js new file mode 100644 index 00000000000000..8d72db8b2f0e63 --- /dev/null +++ b/playground/css/async/black.js @@ -0,0 +1,5 @@ +import { makeText } from './make-text' +import './hotpink.css' +import styles from './black.module.css' + +makeText(styles['black-module'], 'async black') diff --git a/playground/css/async/black.module.css b/playground/css/async/black.module.css new file mode 100644 index 00000000000000..8a609488859d8a --- /dev/null +++ b/playground/css/async/black.module.css @@ -0,0 +1,3 @@ +.black-module { + color: black; +} diff --git a/playground/css/async/blue.js b/playground/css/async/blue.js new file mode 100644 index 00000000000000..a0fc833168cb9b --- /dev/null +++ b/playground/css/async/blue.js @@ -0,0 +1,5 @@ +import { makeText } from './make-text' +import './hotpink.css' +import styles from './blue.module.css' + +makeText(styles['blue-module'], 'async blue') diff --git a/playground/css/async/blue.module.css b/playground/css/async/blue.module.css new file mode 100644 index 00000000000000..cdbea06a0ba2b0 --- /dev/null +++ b/playground/css/async/blue.module.css @@ -0,0 +1,3 @@ +.blue-module { + color: blue; +} diff --git a/playground/css/async/hotpink.css b/playground/css/async/hotpink.css new file mode 100644 index 00000000000000..a79d1ee0de1f48 --- /dev/null +++ b/playground/css/async/hotpink.css @@ -0,0 +1,3 @@ +.hotpink { + color: hotpink; +} diff --git a/playground/css/async/index.js b/playground/css/async/index.js index ba744f629867d9..5d2719c160ae9d 100644 --- a/playground/css/async/index.js +++ b/playground/css/async/index.js @@ -2,3 +2,5 @@ import('./async-1.js') import('./async-2.js') import('./async-3.js') import('./diamond.js') +import('./blue.js') +import('./black.js') diff --git a/playground/css/async/make-text.js b/playground/css/async/make-text.js new file mode 100644 index 00000000000000..dfd84ce8d7552c --- /dev/null +++ b/playground/css/async/make-text.js @@ -0,0 +1,6 @@ +export function makeText(className, content) { + const div = document.createElement('div') + div.className = `base hotpink ${className}` + document.body.appendChild(div) + div.textContent = `${content} ${getComputedStyle(div).color}` +}