Skip to content

Commit

Permalink
Improved inline script minification (#262)
Browse files Browse the repository at this point in the history
* move inline script to a separate file for free minificaiton

* cleanup

* simplify
  • Loading branch information
pacocoursey authored Mar 13, 2024
1 parent 83ad33a commit f5f06c8
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 99 deletions.
6 changes: 1 addition & 5 deletions examples/example/pages/dark.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import Link from 'next/link'

const Page = () => {
return (
<Link href="/">
<a>Go back home</a>
</Link>
)
return <Link href="/">Go back home</Link>
}

Page.theme = 'dark'
Expand Down
8 changes: 1 addition & 7 deletions examples/example/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,7 @@ const Index = () => {
<br />

<div>
<Link href="/dark">
<a>Forced Dark Page</a>
</Link>{' '}
{' '}
<Link href="/light">
<a>Forced Light Page</a>
</Link>
<Link href="/dark">Forced Dark Page</Link><Link href="/light">Forced Light Page</Link>
</div>
</div>
)
Expand Down
6 changes: 1 addition & 5 deletions examples/example/pages/light.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import Link from 'next/link'

const Page = () => {
return (
<Link href="/">
<a>Go back home</a>
</Link>
)
return <Link href="/">Go back home</Link>
}

Page.theme = 'light'
Expand Down
107 changes: 25 additions & 82 deletions next-themes/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as React from 'react'
import type { UseThemeProps, ThemeProviderProps } from './types'
import { script } from './script'

const colorSchemes = ['light', 'dark']
const MEDIA = '(prefers-color-scheme: dark)'
Expand Down Expand Up @@ -158,7 +159,6 @@ const Theme = ({
attribute,
value,
children,
attrs,
nonce
}}
/>
Expand All @@ -177,88 +177,31 @@ const ThemeScript = React.memo(
enableColorScheme,
defaultTheme,
value,
attrs,
themes,
nonce
}: ThemeProviderProps & { attrs: string[]; defaultTheme: string }) => {
const defaultSystem = defaultTheme === 'system'

// Code-golfing the amount of characters in the script
const optimization = (() => {
if (attribute === 'class') {
const removeClasses = `c.remove(${attrs.map((t: string) => `'${t}'`).join(',')})`

return `var d=document.documentElement,c=d.classList;${removeClasses};`
} else {
return `var d=document.documentElement,n='${attribute}',s='setAttribute';`
}
})()

const fallbackColorScheme = (() => {
if (!enableColorScheme) {
return ''
}

const fallback = colorSchemes.includes(defaultTheme) ? defaultTheme : null

if (fallback) {
return `if(e==='light'||e==='dark'||!e)d.style.colorScheme=e||'${defaultTheme}'`
} else {
return `if(e==='light'||e==='dark')d.style.colorScheme=e`
}
})()

const updateDOM = (name: string, literal: boolean = false, setColorScheme = true) => {
const resolvedName = value ? value[name] : name
const val = literal ? name + `|| ''` : `'${resolvedName}'`
let text = ''

// MUCH faster to set colorScheme alongside HTML attribute/class
// as it only incurs 1 style recalculation rather than 2
// This can save over 250ms of work for pages with big DOM
if (enableColorScheme && setColorScheme && !literal && colorSchemes.includes(name)) {
text += `d.style.colorScheme = '${name}';`
}

if (attribute === 'class') {
if (literal || resolvedName) {
text += `c.add(${val})`
} else {
text += `null`
}
} else {
if (resolvedName) {
text += `d[s](n,${val})`
}
}

return text
}

const scriptSrc = (() => {
if (forcedTheme) {
return `!function(){${optimization}${updateDOM(forcedTheme)}}()`
}

if (enableSystem) {
return `!function(){try{${optimization}var e=localStorage.getItem('${storageKey}');if('system'===e||(!e&&${defaultSystem})){var t='${MEDIA}',m=window.matchMedia(t);if(m.media!==t||m.matches){${updateDOM(
'dark'
)}}else{${updateDOM('light')}}}else if(e){${
value ? `var x=${JSON.stringify(value)};` : ''
}${updateDOM(value ? `x[e]` : 'e', true)}}${
!defaultSystem ? `else{` + updateDOM(defaultTheme, false, false) + '}' : ''
}${fallbackColorScheme}}catch(e){}}()`
}

return `!function(){try{${optimization}var e=localStorage.getItem('${storageKey}');if(e){${
value ? `var x=${JSON.stringify(value)};` : ''
}${updateDOM(value ? `x[e]` : 'e', true)}}else{${updateDOM(
defaultTheme,
false,
false
)};}${fallbackColorScheme}}catch(t){}}();`
})()

return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: scriptSrc }} />
}: ThemeProviderProps & { defaultTheme: string }) => {
const scriptProps = [
attribute,
storageKey,
defaultTheme,
forcedTheme,
themes,
value,
enableSystem,
enableColorScheme
]

return (
<script
suppressHydrationWarning
nonce={typeof window === 'undefined' ? nonce : ''}
dangerouslySetInnerHTML={{
__html: `(${script.toString()})(${scriptProps
.map(p => JSON.stringify(p || null))
.join(',')})`
}}
/>
)
}
)

Expand Down
59 changes: 59 additions & 0 deletions next-themes/src/script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export const script = (
attribute,
storageKey,
defaultTheme,
forcedTheme,
themes,
value,
enableSystem,
enableColorScheme
) => {
const el = document.documentElement
const media = `(prefers-color-scheme: dark)`
const systemThemes = ['light', 'dark']
const isClass = attribute === 'class'
const classes =
isClass && !!value
? themes.map(t => {
return value[t] || t
})
: themes

function updateDOM(theme: string) {
if (isClass) {
el.classList.remove(...classes)
el.classList.add(theme)
} else {
el.setAttribute(attribute, theme)
}

setColorScheme(theme)
}

function setColorScheme(theme: string) {
if (enableColorScheme && systemThemes.includes(theme)) {
el.style.colorScheme = theme
}
}

function getSystemTheme() {
return window.matchMedia(media).matches ? 'dark' : 'light'
}

;(function () {
if (forcedTheme) {
updateDOM(forcedTheme)
} else {
try {
const e = localStorage.getItem(storageKey)
const themeName = e || defaultTheme
const isSystem = enableSystem && themeName === 'system' ? true : false

const theme = isSystem ? getSystemTheme() : e || defaultTheme
updateDOM(theme)
} catch (e) {
//
}
}
})()
}

0 comments on commit f5f06c8

Please sign in to comment.