Skip to content

Commit

Permalink
fix: move theme styles below other styles (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker authored Dec 14, 2023
1 parent fa1b759 commit c80aad5
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 43 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ What this plugin does is:
- Collect all `eufemia-theme` files (`{scss,css}`) – also check if they are located in `/src` or needs to be collected from `/build`. Both are used by the Eufemia repo/portal.
- After we have collected all available theme files, we create or update a static import `load-eufemia-styles.js`, which is git-ignored.
- Split theme styles into separate CSS files (Webpack chunks) inside `gatsby-node.js`
- Inserts some JavaScript in the HTML head in order to handle what theme file should be shown (`inlineScriptProd` and `inlineScriptDev`)
- Inserts some JavaScript in the HTML head in order to handle what theme file should be shown (`inlineScript` and `inlineScriptDev`)
- Load these inline scripts via Webpack inline module loaders: `!raw-loader!terser-loader!`
- By using localStorage, we block the HTML rendering, this way we do avoid flickering of a default theme

Expand All @@ -147,6 +147,10 @@ During dev, we do not get any inline styles from Gatsby – they are handled by
- Use `uniqueId` to reload css files as there is not unique build hash, unlike we get during production
- Use `MutationObserver` to reload the current theme file, because Webpack uses hot module replacement, so we need to reload as well

### Sorting order

The order of the extracted styles can influence CSS specificity. Therefore, the extracted theme styles (`/ui.css`) should always be placed below the `/commons.css`.

## Releases

Releases are made with [semantic-release](https://github.com/semantic-release/semantic-release).
Expand Down
2 changes: 1 addition & 1 deletion examples/shared/ChangeStyleTheme.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Dropdown, Switch } from '@dnb/eufemia'
import { Dropdown } from '@dnb/eufemia'
import { Context } from '@dnb/eufemia/shared'
import {
getThemes,
Expand Down
1 change: 1 addition & 0 deletions examples/ui-theme-example/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Anchor, Button } from '@dnb/eufemia'
import { Layout } from '@dnb/eufemia/extensions/forms'
import ChangeStyleTheme from '../../../shared/ChangeStyleTheme'
import { Link } from 'gatsby'
import './style.css'

const App = () => {
return (
Expand Down
3 changes: 3 additions & 0 deletions examples/ui-theme-example/src/pages/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background: white;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
],
"scripts": {
"test": "yarn workspace e2e test",
"watch": "yarn workspace gatsby-plugin-eufemia-theme-handler watch",
"watch": "yarn workspace gatsby-plugin-eufemia-theme-handler build:watch",
"watch:all": "yarn watch & yarn workspace sb-theme-example watch & yarn workspace ui-theme-example watch & yarn serve",
"start": "yarn watch & yarn workspace sb-theme-example start & yarn workspace ui-theme-example start",
"build": "yarn workspace gatsby-plugin-eufemia-theme-handler build && yarn workspace ui-theme-example build && yarn workspace ui-theme-example serve & yarn workspace sb-theme-example build",
Expand Down
8 changes: 5 additions & 3 deletions packages/e2e/tests/change-theme.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,22 @@ test.describe('change theme', () => {
expect(uiStyleElementExists).toBeNull()
})

test('should load css file after template', async ({ page }) => {
test('should place css file after commons.css', async ({ page }) => {
await page.click('#change-theme')
await page.click('#change-theme-portal ul li:nth-child(2)')

const sbanken = 'link[href^="/sbanken."][rel="stylesheet"]'
const sbankenCssAfterTemplateExists = await page.$(
'#eufemia-style-theme + link[href^="/sbanken."][rel="stylesheet"]'
`style[data-href^="/commons."] ~ ${sbanken}, link[href^="/commons."] ~ ${sbanken}`
)
expect(sbankenCssAfterTemplateExists).toBeTruthy()

await page.click('#change-theme')
await page.click('#change-theme-portal ul li:first-child')

const ui = 'link[href^="/ui."][rel="stylesheet"]'
const uiCssAfterTemplateExists = await page.$(
'#eufemia-style-theme + link[href^="/ui."][rel="stylesheet"]'
`style[data-href^="/commons."] ~ ${ui}, link[href^="/commons."] ~ ${ui}`
)
expect(uiCssAfterTemplateExists).toBeTruthy()
})
Expand Down
4 changes: 2 additions & 2 deletions packages/gatsby-plugin-eufemia-theme-handler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"./themeHandler.js": "./themeHandler.js",
"./themeHandler.d.ts": "./themeHandler.d.ts",
"./inlineScriptDev.js": "./inlineScriptDev.js",
"./inlineScriptProd.js": "./inlineScriptProd.js",
"./inlineScript.js": "./inlineScript.js",
"./gatsby-node.js": "./gatsby-node.js",
"./gatsby-browser.js": "./gatsby-browser.js",
"./gatsby-ssr.js": "./gatsby-ssr.js",
Expand All @@ -44,8 +44,8 @@
"build": "yarn build:cmd && GATSBY_FILES=true yarn build:cmd && yarn build:types",
"build:cmd": "yarn babel src --extensions .js,.ts,.tsx,.mjs --out-dir .",
"build:types": "tsc --project tsconfig.json",
"build:watch": "yarn build:cmd --watch",
"clean": "rm !(src|babel.config.js|.gitignore|.npmignore|LICENSE|.env|*.md|*.json)",
"watch": "yarn build --watch",
"test:types": "tsc --noEmit"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ if (typeof window !== 'undefined') {
callback = null
) => {
try {
const headElement = document.head
const styleElement = document.getElementById('eufemia-style-theme')
const headElement = document.querySelector('html head')
const themes = globalThis.availableThemes
const theme = themes[themeName]
const href = theme.file + (reload ? '?' + Date.now() : '')

if (!theme) {
console.error('No theme found:', themeName)
return // stop here
}

const href = theme.file + (reload ? '?' + Date.now() : '')

/**
* To avoid flicker during change
*/
Expand Down Expand Up @@ -70,6 +71,8 @@ if (typeof window !== 'undefined') {
if (previousElem) {
headElement.removeChild(previousElem)
}

reorderStyles()
} catch (e) {
console.error(e)
}
Expand Down Expand Up @@ -107,5 +110,34 @@ if (typeof window !== 'undefined') {
}

const themeName = globalThis.__getEufemiaThemeName()
globalThis.__updateEufemiaThemeFile(themeName)
const isDefaultTheme = themeName === globalThis.defaultTheme
if (!isDefaultTheme) {
globalThis.__updateEufemiaThemeFile(themeName)
}
}

function reorderStyles() {
const commonsElement = getCommonsElement()
if (commonsElement) {
moveElementBelow(
document.getElementById('current-theme'),
commonsElement
)
moveElementBelow(
document.getElementById('eufemia-style-theme'),
commonsElement
)
}
}

function getCommonsElement() {
const elements = Array.from(document.head.querySelectorAll('link[href]'))
return elements.find(({ href }) => {
return href.includes('commons')
})
}

function moveElementBelow(elementToMove, targetElement) {
const parentElement = targetElement.parentNode
parentElement.insertBefore(elementToMove, targetElement.nextSibling)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
if (typeof window !== 'undefined') {
try {
const headElement = document.querySelector('html head')
const logMutations = (mutations) => {
for (const mutation of mutations) {
const element = mutation.nextSibling
if (
element &&
(element.src || element.href || '').includes('/commons.')
) {
const themeName = globalThis.__getEufemiaThemeName()
globalThis.__updateEufemiaThemeFile(themeName, true)
break
}
if (!window.__hasEufemiaObserver) {
window.__hasEufemiaObserver = true
onElementInsertion('[href="/commons.css"]', () => {
const themeName = globalThis.__getEufemiaThemeName()
globalThis.__updateEufemiaThemeFile(themeName, true)
})
}
}

function onElementInsertion(targetSelector, callback) {
const headElement = document.head

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
const addedNodes = Array.from(mutation.addedNodes)

const targetElementAdded = addedNodes.find((node) => {
return (
node.nodeType === Node.ELEMENT_NODE &&
node.matches(targetSelector)
)
})

if (targetElementAdded) {
callback(targetElementAdded, observer)
}
}
})
})

const observer = new MutationObserver(logMutations)
observer.observe(headElement, { childList: true })
} catch (e) {
console.error(e)
}
observer.observe(headElement, {
childList: true,
})
}
99 changes: 86 additions & 13 deletions packages/gatsby-plugin-eufemia-theme-handler/src/themeHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import { Theme } from '@dnb/eufemia/shared'
import inlineScriptProd from '!raw-loader!terser-loader!./inlineScriptProd'
import inlineScript from '!raw-loader!terser-loader!./inlineScript'
import inlineScriptDev from '!raw-loader!terser-loader!./inlineScriptDev'
import EventEmitter from './EventEmitter.js'
import EventEmitter from './EventEmitter'

import type { ThemeNames, ThemeProps } from '@dnb/eufemia/shared/Theme'

Expand Down Expand Up @@ -109,9 +109,9 @@ export const onPreRenderHTML = (
// Make themes to not be embedded, but rather load as css files
if (!isDev) {
let defaultElement
for (const element of headComponents) {
const href = element.props['data-href']
if (href && href.includes('.css')) {
for (const item of headComponents) {
const href = item?.props?.['data-href']
if (href?.includes('.css')) {
if (
availableThemesArray.some((name) => {
return href.includes(`/${name}.`)
Expand All @@ -129,14 +129,14 @@ export const onPreRenderHTML = (
pluginOptions?.inlineDefaultTheme &&
themeName === defaultTheme
) {
defaultElement = element
headComponents[element] = null
defaultElement = item
headComponents[item] = null
} else {
// Remove the inline style
// but not when its the default theme
delete element.props['data-href']
delete element.props['data-identity']
delete element.props.dangerouslySetInnerHTML
delete item.props['data-href']
delete item.props['data-identity']
delete item.props.dangerouslySetInnerHTML
}
}
}
Expand Down Expand Up @@ -206,7 +206,7 @@ export const onPreRenderHTML = (
<script
key="eufemia-style-theme-script-prod"
dangerouslySetInnerHTML={{
__html: replaceGlobalVars(inlineScriptProd),
__html: replaceGlobalVars(inlineScript),
}}
/>
)
Expand Down Expand Up @@ -236,14 +236,20 @@ export const onPreRenderHTML = (
headComponents.forEach((item) => {
const exists = uniqueHeadComponents.some((i) => {
const href = i?.props?.['data-href']
return href && item?.props?.['data-href'] === href
return href && href === item?.props?.['data-href']
})
if (!exists) {
uniqueHeadComponents.push(item)
}
})

replaceHeadComponents(uniqueHeadComponents)
const orderedComponents = changeOrderOfComponents(
uniqueHeadComponents,
'props.data-href',
['commons', ...availableThemesArray]
)

replaceHeadComponents(orderedComponents)
}
}

Expand All @@ -260,3 +266,70 @@ export function wrapRootElement({ element }, pluginOptions) {

return element
}

function changeOrderOfComponents(
arr: Array<Record<string, unknown>>,
propertyPath: string,
order: Array<string>
) {
const copyArr = [...arr]

const findIndexByMatch = (obj, path, value) => {
const keys = path.split('.')
let currentObj = obj
for (const key of keys) {
if (currentObj[key] === undefined) {
return -1 // Property doesn't exist in the object
}
currentObj = currentObj[key]
}

if (Array.isArray(currentObj)) {
return currentObj.findIndex((item) => item.includes(value))
} else if (typeof currentObj === 'string') {
return currentObj.includes(value) ? 0 : -1
}

return -1
}

const validOrder = order.filter((value) =>
copyArr.some(
(obj) => findIndexByMatch(obj, propertyPath, value) !== -1
)
)

// Check if the order is already correct
if (
validOrder.every(
(value, index) =>
copyArr[index] &&
findIndexByMatch(copyArr[index], propertyPath, value) !== -1
)
) {
return copyArr
}

const validIndexes = validOrder.map((value) =>
copyArr.findIndex(
(obj) => findIndexByMatch(obj, propertyPath, value) !== -1
)
)

// Check if the order needs to be switched
if (
validIndexes.length === validOrder.length &&
!validIndexes.every((value, index) => value === index)
) {
validIndexes.forEach((value, index) => {
if (value !== index) {
;[copyArr[index], copyArr[value]] = [
copyArr[value],
copyArr[index],
]
}
})
}

return copyArr
}

0 comments on commit c80aad5

Please sign in to comment.