Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/oxc/src/app/plugins/color-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { installColorMode } from '@vitejs/devtools-ui/plugins/color-mode'
import { defineNuxtPlugin } from '#app/nuxt'

export default defineNuxtPlugin(nuxtApp => {
installColorMode(nuxtApp)
})
5 changes: 5 additions & 0 deletions packages/oxc/src/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fileURLToPath } from 'node:url'
import { getColorSchemeHeadScript } from '@vitejs/devtools-ui/utils/color-scheme'
import { defineNuxtConfig } from 'nuxt/config'
import { alias } from '../../../alias'

Expand Down Expand Up @@ -57,6 +58,10 @@ export default defineNuxtConfig({
href: '/favicon.svg',
},
],
script: [
// Anti-FOUC: apply the resolved color scheme before first paint.
{ innerHTML: getColorSchemeHeadScript(), tagPosition: 'head' },
],
htmlAttrs: {
lang: 'en',
class: 'bg-dots',
Expand Down
6 changes: 6 additions & 0 deletions packages/rolldown/src/app/plugins/color-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { installColorMode } from '@vitejs/devtools-ui/plugins/color-mode'
import { defineNuxtPlugin } from '#app/nuxt'

export default defineNuxtPlugin((nuxtApp) => {
installColorMode(nuxtApp)
})
5 changes: 5 additions & 0 deletions packages/rolldown/src/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { getColorSchemeHeadScript } from '@vitejs/devtools-ui/utils/color-scheme'
import { defineNuxtConfig } from 'nuxt/config'
import Inspect from 'vite-plugin-inspect'
import { alias } from '../../../alias'
Expand Down Expand Up @@ -82,6 +83,10 @@ export default defineNuxtConfig({
link: [
{ rel: 'icon', type: 'image/svg+xml', href: `/favicon.svg` },
],
script: [
// Anti-FOUC: apply the resolved color scheme before first paint.
{ innerHTML: getColorSchemeHeadScript(), tagPosition: 'head' },
],
htmlAttrs: {
lang: 'en',
class: 'bg-dots',
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/src/composables/dark.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useDark } from '@vueuse/core'
import { COLOR_SCHEME_LIGHT, COLOR_SCHEME_STORAGE_KEY } from '../utils/color-scheme'

export const isDark = useDark({
storageKey: 'vite-devtools-color-scheme',
valueLight: 'light',
storageKey: COLOR_SCHEME_STORAGE_KEY,
valueLight: COLOR_SCHEME_LIGHT,
})

export function toggleDark() {
Expand Down
36 changes: 36 additions & 0 deletions packages/ui/src/plugins/color-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { watch } from 'vue'
import { isDark } from '../composables/dark'
import { COLOR_SCHEME_DARK, COLOR_SCHEME_LIGHT } from '../utils/color-scheme'

/**
* Minimal structural type for the Nuxt app, avoiding a hard dependency on
* Nuxt's types from within the shared UI package.
*/
interface NuxtAppLike {
hook: (name: 'app:mounted', cb: () => void) => void
}

function applyColorScheme(dark: boolean): void {
const el = document.documentElement
el.classList.add(dark ? COLOR_SCHEME_DARK : COLOR_SCHEME_LIGHT)
el.classList.remove(dark ? COLOR_SCHEME_LIGHT : COLOR_SCHEME_DARK)
el.style.colorScheme = dark ? COLOR_SCHEME_DARK : COLOR_SCHEME_LIGHT
}

/**
* Ensures the resolved color scheme is applied within the Nuxt app lifecycle.
*
* The `isDark` singleton is created via `useDark()` at module scope, so its own
* `tryOnMounted` re-sync never fires (no component instance). Nuxt/unhead writes
* `htmlAttrs.class` during mount, which can drop the theme class applied at
* import time and leave the UI stuck in light mode until an unrelated reactive
* flush (cursor move / scroll). Re-applying on `app:mounted` — after unhead has
* set the attributes — fixes that. The `watch` keeps it in sync for runtime
* toggles and OS theme changes.
*/
export function installColorMode(nuxtApp: NuxtAppLike): void {
nuxtApp.hook('app:mounted', () => {
applyColorScheme(isDark.value)
watch(isDark, applyColorScheme, { flush: 'post' })
})
}
35 changes: 35 additions & 0 deletions packages/ui/src/utils/color-scheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Shared color-scheme constants and the anti-FOUC inline script.
*
* This module must stay side-effect free (no VueUse / no `dark.ts` import) so
* it can be imported from `nuxt.config.ts` at build time in Node, where
* `window` is unavailable.
*
* The storage key and class names are the single source of truth shared by the
* `useDark()` singleton in `../composables/dark` and the inline head script,
* so the two can never drift apart.
*/

export const COLOR_SCHEME_STORAGE_KEY = 'vite-devtools-color-scheme'
export const COLOR_SCHEME_DARK = 'dark'
export const COLOR_SCHEME_LIGHT = 'light'

/**
* Returns a minified IIFE to be inlined in `<head>` so the resolved theme is
* applied to `<html>` before first paint, eliminating the flash of light theme.
*
* It mirrors `useDark`'s `auto` resolution exactly: honor a stored
* `dark`/`light` value, otherwise fall back to the OS `prefers-color-scheme`.
*/
export function getColorSchemeHeadScript(): string {
const key = JSON.stringify(COLOR_SCHEME_STORAGE_KEY)
const dark = JSON.stringify(COLOR_SCHEME_DARK)
const light = JSON.stringify(COLOR_SCHEME_LIGHT)
return `;(function(){try{`
+ `var v=localStorage.getItem(${key});`
+ `var d=v===${dark}||((v===null||v==='auto')&&matchMedia('(prefers-color-scheme: dark)').matches);`
+ `var el=document.documentElement;`
+ `el.classList.add(d?${dark}:${light});el.classList.remove(d?${light}:${dark});`
+ `el.style.colorScheme=d?${dark}:${light};`
+ `}catch(e){}})();`
}
6 changes: 6 additions & 0 deletions packages/vite/src/app/plugins/color-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { installColorMode } from '@vitejs/devtools-ui/plugins/color-mode'
import { defineNuxtPlugin } from '#app/nuxt'

export default defineNuxtPlugin((nuxtApp) => {
installColorMode(nuxtApp)
})
5 changes: 5 additions & 0 deletions packages/vite/src/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import process from 'node:process'
import { getColorSchemeHeadScript } from '@vitejs/devtools-ui/utils/color-scheme'
import { defineNuxtConfig } from 'nuxt/config'
import { alias } from '../../../alias'
import '@nuxt/eslint'
Expand Down Expand Up @@ -75,6 +76,10 @@ export default defineNuxtConfig({
link: [
{ rel: 'icon', type: 'image/svg+xml', href: `/favicon.svg` },
],
script: [
// Anti-FOUC: apply the resolved color scheme before first paint.
{ innerHTML: getColorSchemeHeadScript(), tagPosition: 'head' },
],
htmlAttrs: {
lang: 'en',
class: 'bg-dots',
Expand Down
Loading