Skip to content

Commit

Permalink
chore(editor): upgrading twind to support new tailwind 3 syntax (#6024)
Browse files Browse the repository at this point in the history
This PR upgrades [twind](https://twind.style/) to its latest version,
adjusts the initialization code, and enables support for Tailwind's
dynamic class names:

<video
src="https://github.com/concrete-utopia/utopia/assets/7003853/49a8ff00-524d-4fb4-a1c7-9b852ce6f628"></video>



**Manual Tests:**
I hereby swear that:

- [X] I opened a hydrogen project and it loaded
- [X] I could navigate to various routes in Preview mode
  • Loading branch information
liady authored Jun 27, 2024
1 parent f5b242e commit 34e0b4b
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 44 deletions.
4 changes: 3 additions & 1 deletion editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
"@stitches/react": "1.2.8",
"@svgr/plugin-jsx": "5.5.0",
"@tippyjs/react": "4.1.0",
"@twind/core": "1.1.3",
"@twind/preset-autoprefix": "1.0.7",
"@twind/preset-tailwind": "1.1.4",
"@types/fontfaceobserver": "0.0.6",
"@types/lodash.findlastindex": "4.6.7",
"@types/react-syntax-highlighter": "11.0.4",
Expand Down Expand Up @@ -271,7 +274,6 @@
"string-hash": "1.1.3",
"strip-ansi": "6.0.0",
"tippy.js": "6.2.6",
"twind": "0.16.16",
"typescript": "5.2.2",
"typescript-for-the-editor": "npm:[email protected]",
"url-join": "4.0.1",
Expand Down
73 changes: 54 additions & 19 deletions editor/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 50 additions & 24 deletions editor/src/core/tailwind/tailwind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ import { isRight, left, right } from '../shared/either'
import type { RequireFn } from '../shared/npm-dependency-types'
import type { ProjectFile } from '../shared/project-file-types'
import { isTextFile } from '../shared/project-file-types'
import type { Configuration, Sheet } from 'twind'
import { silent } from 'twind'
import type { TwindObserver } from 'twind/observe'
import { create, observe, cssomSheet } from 'twind/observe'
import type { Sheet, Twind } from '@twind/core'
import { cssom, observe, defineConfig, tw } from '@twind/core'
import presetAutoprefix from '@twind/preset-autoprefix'
import presetTailwind from '@twind/preset-tailwind'
import React from 'react'
import { includesDependency } from '../../components/editor/npm-dependency/npm-dependency'
import { propOrNull } from '../shared/object-utils'
import { memoize } from '../shared/memoize'
import { importDefault } from '../es-modules/commonjs-interop'
import { PostCSSPath, TailwindConfigPath } from './tailwind-config'
import { useKeepReferenceEqualityIfPossible } from '../../utils/react-performance'
import { twind } from '@twind/core'

type TwindConfigType = ReturnType<typeof defineConfig>

function hasRequiredDependenciesForTailwind(packageJsonFile: ProjectFile): boolean {
const hasTailwindDependency = includesDependency(packageJsonFile, 'tailwindcss')
Expand Down Expand Up @@ -86,19 +89,30 @@ function enablesPreflight(tailwindConfig: any): boolean {
return true
}

function convertTailwindToTwindConfig(tailwindConfig: any): Configuration {
function convertTailwindToTwindConfig(tailwindConfig: any): TwindConfigType {
const preflightEnabled = enablesPreflight(tailwindConfig)

return {
...tailwindConfig,
preflight: preflightEnabled,
}
const twindConfig = defineConfig({
presets: [
presetTailwind({
disablePreflight: !preflightEnabled,
}),
presetAutoprefix(),
],
// force pushing tailwind's config to twind
theme: tailwindConfig.theme,
darkMode: tailwindConfig.darkMode,
variants: tailwindConfig.variants,
preflight: tailwindConfig.preflight,
})

return twindConfig
}

function getTailwindConfig(
tailwindFile: ProjectFile | null,
requireFn: RequireFn,
): Either<any, Configuration> {
): Either<any, TwindConfigType> {
if (tailwindFile != null && isTextFile(tailwindFile)) {
try {
const requireResult = requireFn('/', TailwindConfigPath)
Expand Down Expand Up @@ -127,22 +141,22 @@ function useGetTailwindConfigFile(projectContents: ProjectContentTreeRoot): Proj
function useGetTailwindConfig(
projectContents: ProjectContentTreeRoot,
requireFn: RequireFn,
): Configuration {
): TwindConfigType {
const tailwindConfigFile = useGetTailwindConfigFile(projectContents)
const tailwindConfig = React.useMemo(() => {
const maybeConfig = getTailwindConfig(tailwindConfigFile, requireFn)
if (isRight(maybeConfig)) {
return maybeConfig.value
} else {
return {}
return defineConfig({})
}
}, [tailwindConfigFile, requireFn])
return useKeepReferenceEqualityIfPossible(tailwindConfig)
}

interface TwindInstance {
element: HTMLStyleElement
observer: TwindObserver
instance: Twind
}

let twindInstance: TwindInstance | null = null
Expand All @@ -153,7 +167,8 @@ export function isTwindEnabled(): boolean {

function clearTwind() {
if (twindInstance != null) {
twindInstance.observer.disconnect()
twindInstance.instance.clear()
twindInstance.instance.destroy()
twindInstance.element.parentNode?.removeChild(twindInstance.element)
}
}
Expand Down Expand Up @@ -197,29 +212,40 @@ const adjustRuleScope = memoize(adjustRuleScopeImpl, {
matchesArg: (a, b) => a === b,
})

function updateTwind(config: Configuration, prefixSelector: string | null) {
function updateTwind(config: TwindConfigType, prefixSelector: string | null) {
const element = document.head.appendChild(document.createElement('style'))
element.appendChild(document.createTextNode('')) // Avoid Edge bug where empty style elements doesn't create sheets
element.setAttribute('id', `twind-styles-${Math.random().toString(36).slice(2)}`)

const sheet = cssomSheet({ target: element.sheet ?? undefined })
const sheet = cssom(element)
const customSheet: Sheet = {
...sheet,
target: sheet.target,
insert: (rule, index) => {
insert: (rule, index, sheetRule) => {
const scopedRule = adjustRuleScope(rule, prefixSelector)
sheet.insert(scopedRule, index)
sheet.insert(scopedRule, index, sheetRule)
},
}

clearTwind()

const observer = observe(
document.documentElement,
create({ ...config, sheet: customSheet, mode: silent }),
)
if (twindInstance == null) {
const prefixes = ['TWIND_', 'TAILWIND_']
window.addEventListener('warning', (event: any | { detail: { code: string } }) => {
const isTwindWarning: boolean = prefixes.some((prefix) =>
event?.detail?.code?.startsWith?.(prefix),
)
if (isTwindWarning) {
event.preventDefault()
}
})
}

const instance = observe(twind(config, customSheet), document.documentElement)

twindInstance = {
element: element,
observer: observer,
instance: instance,
}
}

Expand Down Expand Up @@ -255,7 +281,7 @@ export function injectTwind(
const shouldUseTwind = hasDependencies && hasPostCSSPlugin
const tailwindConfigFile = getProjectFileByFilePath(projectContents, TailwindConfigPath)
const maybeTailwindConfig = getTailwindConfig(tailwindConfigFile, requireFn)
const tailwindConfig = isRight(maybeTailwindConfig) ? maybeTailwindConfig.value : {}
const tailwindConfig = isRight(maybeTailwindConfig) ? maybeTailwindConfig.value : defineConfig({})
if (shouldUseTwind) {
updateTwind(tailwindConfig, prefixSelector)
} else {
Expand Down

0 comments on commit 34e0b4b

Please sign in to comment.