diff --git a/editor/package.json b/editor/package.json index 35c50e5dbf6f..f45e99062113 100644 --- a/editor/package.json +++ b/editor/package.json @@ -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", @@ -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:typescript@4.6.4", "url-join": "4.0.1", diff --git a/editor/pnpm-lock.yaml b/editor/pnpm-lock.yaml index a7acf6d7be82..040204595233 100644 --- a/editor/pnpm-lock.yaml +++ b/editor/pnpm-lock.yaml @@ -81,6 +81,9 @@ specifiers: '@svgr/plugin-jsx': 5.5.0 '@testing-library/react': 14.0.0 '@tippyjs/react': 4.1.0 + '@twind/core': 1.1.3 + '@twind/preset-autoprefix': 1.0.7 + '@twind/preset-tailwind': 1.1.4 '@types/babel__traverse': 7.18.3 '@types/chai': 3.5.1 '@types/chroma-js': 2.0.0 @@ -322,7 +325,6 @@ specifiers: ts-loader: 5.3.3 ts-unused-exports: 9.0.5 tsify: 3.0.1 - twind: 0.16.16 typescript: 5.2.2 typescript-for-the-editor: npm:typescript@4.6.4 url-join: 4.0.1 @@ -374,6 +376,9 @@ dependencies: '@stitches/react': 1.2.8_react@18.1.0 '@svgr/plugin-jsx': 5.5.0 '@tippyjs/react': 4.1.0_ef5jwxihqo6n7gxfmzogljlgcm + '@twind/core': 1.1.3_typescript@5.2.2 + '@twind/preset-autoprefix': 1.0.7_n5n7exiee3gfge3vguymadfcha + '@twind/preset-tailwind': 1.1.4_n5n7exiee3gfge3vguymadfcha '@types/fontfaceobserver': 0.0.6 '@types/lodash.findlastindex': 4.6.7 '@types/react-syntax-highlighter': 11.0.4 @@ -484,7 +489,6 @@ dependencies: string-hash: 1.1.3 strip-ansi: 6.0.0 tippy.js: 6.2.6 - twind: 0.16.16_typescript@5.2.2 typescript: 5.2.2 typescript-for-the-editor: /typescript/4.6.4 url-join: 4.0.1 @@ -4523,6 +4527,48 @@ packages: resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} dev: false + /@twind/core/1.1.3_typescript@5.2.2: + resolution: {integrity: sha512-/B/aNFerMb2IeyjSJy3SJxqVxhrT77gBDknLMiZqXIRr4vNJqiuhx7KqUSRzDCwUmyGuogkamz+aOLzN6MeSLw==} + engines: {node: '>=14.15.0'} + peerDependencies: + typescript: ^4.8.4 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + csstype: 3.1.2 + typescript: 5.2.2 + dev: false + + /@twind/preset-autoprefix/1.0.7_n5n7exiee3gfge3vguymadfcha: + resolution: {integrity: sha512-3wmHO0pG/CVxYBNZUV0tWcL7CP0wD5KpyWAQE/KOalWmOVBj+nH6j3v6Y3I3pRuMFaG5DC78qbYbhA1O11uG3w==} + engines: {node: '>=14.15.0'} + peerDependencies: + '@twind/core': ^1.1.0 + typescript: ^4.8.4 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@twind/core': 1.1.3_typescript@5.2.2 + style-vendorizer: 2.2.3 + typescript: 5.2.2 + dev: false + + /@twind/preset-tailwind/1.1.4_n5n7exiee3gfge3vguymadfcha: + resolution: {integrity: sha512-zv85wrP/DW4AxgWrLfH7kyGn/KJF3K04FMLVl2AjoxZGYdCaoZDkL8ma3hzaKQ+WGgBFRubuB/Ku2Rtv/wjzVw==} + engines: {node: '>=14.15.0'} + peerDependencies: + '@twind/core': ^1.1.0 + typescript: ^4.8.4 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@twind/core': 1.1.3_typescript@5.2.2 + typescript: 5.2.2 + dev: false + /@types/aria-query/5.0.1: resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} dev: true @@ -8544,6 +8590,7 @@ packages: domelementtype: 2.3.0 domhandler: 4.3.1 entities: 2.2.0 + dev: true /dom-serializer/2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -8585,6 +8632,7 @@ packages: engines: {node: '>= 4'} dependencies: domelementtype: 2.3.0 + dev: true /domhandler/5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} @@ -8605,6 +8653,7 @@ packages: dom-serializer: 1.3.2 domelementtype: 2.3.0 domhandler: 4.3.1 + dev: true /domutils/3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} @@ -10921,6 +10970,7 @@ packages: domhandler: 4.3.1 domutils: 2.8.0 entities: 2.2.0 + dev: true /htmlparser2/8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} @@ -17911,8 +17961,8 @@ packages: inline-style-parser: 0.1.1 dev: false - /style-vendorizer/2.1.1: - resolution: {integrity: sha512-gVO6Cwxtg8iX0X1W4xMhSc5WbQpiIBQDkhq3JkwebMRRgyhCfuvMrnPlTAGTRjfQPGRmzgjCOZ4drehTnLahHA==} + /style-vendorizer/2.2.3: + resolution: {integrity: sha512-/VDRsWvQAgspVy9eATN3z6itKTuyg+jW1q6UoTCQCFRqPDw8bi3E1hXIKnGw5LvXS2AQPuJ7Af4auTLYeBOLEg==} dev: false /stylis/4.0.10: @@ -18475,21 +18525,6 @@ packages: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} dev: true - /twind/0.16.16_typescript@5.2.2: - resolution: {integrity: sha512-UlAYjkGCdgjg4xU1SIwRW0PpG0anZrY32+kS2jYsi32yb4RuW0ttzaI1OglgwAUk/rZwzoINilnIFORzOSFZag==} - engines: {node: '>=10.13'} - peerDependencies: - typescript: ^4.1.0 - peerDependenciesMeta: - typescript: - optional: true - dependencies: - csstype: 3.0.9 - htmlparser2: 6.1.0 - style-vendorizer: 2.1.1 - typescript: 5.2.2 - dev: false - /type-check/0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} diff --git a/editor/src/core/tailwind/tailwind.ts b/editor/src/core/tailwind/tailwind.ts index 075b42cbce04..77377a196ad7 100644 --- a/editor/src/core/tailwind/tailwind.ts +++ b/editor/src/core/tailwind/tailwind.ts @@ -6,10 +6,10 @@ 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' @@ -17,6 +17,9 @@ 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 function hasRequiredDependenciesForTailwind(packageJsonFile: ProjectFile): boolean { const hasTailwindDependency = includesDependency(packageJsonFile, 'tailwindcss') @@ -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 { +): Either { if (tailwindFile != null && isTextFile(tailwindFile)) { try { const requireResult = requireFn('/', TailwindConfigPath) @@ -127,14 +141,14 @@ 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) @@ -142,7 +156,7 @@ function useGetTailwindConfig( interface TwindInstance { element: HTMLStyleElement - observer: TwindObserver + instance: Twind } let twindInstance: TwindInstance | null = null @@ -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) } } @@ -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, } } @@ -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 {