diff --git a/editor/package.json b/editor/package.json index 9da259561b23..1d4fa750356a 100644 --- a/editor/package.json +++ b/editor/package.json @@ -424,6 +424,7 @@ "source-map-loader": "0.2.3", "string-replace-loader": "2.2.0", "style-loader": "0.18.2", + "tailwindcss": "^3.4.13", "tar": "6.0.5", "terser-webpack-plugin": "5.3.9", "three": "0.140.2", diff --git a/editor/pnpm-lock.yaml b/editor/pnpm-lock.yaml index 9c062e509ccd..fe2bc6505a00 100644 --- a/editor/pnpm-lock.yaml +++ b/editor/pnpm-lock.yaml @@ -318,6 +318,7 @@ specifiers: string-replace-loader: 2.2.0 strip-ansi: 6.0.0 style-loader: 0.18.2 + tailwindcss: ^3.4.13 tar: 6.0.5 terser-webpack-plugin: 5.3.9 three: 0.140.2 @@ -643,6 +644,7 @@ devDependencies: source-map-loader: 0.2.3 string-replace-loader: 2.2.0_webpack@5.88.2 style-loader: 0.18.2 + tailwindcss: 3.4.13 tar: 6.0.5 terser-webpack-plugin: 5.3.9_webpack@5.88.2 three: 0.140.2 @@ -669,7 +671,6 @@ packages: /@alloc/quick-lru/5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - dev: false /@alloc/types/1.3.0: resolution: {integrity: sha512-mH7LiFiq9g6rX2tvt1LtwsclfG5hnsmtIfkZiauAGrm1AwXhoRS0sF2WrN9JGN7eV5vFXqNaB0eXZ3IvMsVi9g==} @@ -4649,7 +4650,7 @@ packages: dependencies: '@types/estree': 0.0.39 estree-walker: 1.0.1 - picomatch: 2.3.0 + picomatch: 2.3.1 dev: true /@rollup/pluginutils/4.2.1: @@ -6428,7 +6429,6 @@ packages: /arg/5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -7459,7 +7459,6 @@ packages: /camelcase-css/2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - dev: false /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} @@ -8831,7 +8830,6 @@ packages: /didyoumean/1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: false /diff-match-patch/1.0.5: resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} @@ -8887,7 +8885,6 @@ packages: /dlv/1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: false /dnd-core/16.0.1: resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==} @@ -10875,7 +10872,6 @@ packages: engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 - dev: false /glob-to-regexp/0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -10902,7 +10898,6 @@ packages: minipass: 7.1.2 package-json-from-dist: 1.0.0 path-scurry: 1.11.1 - dev: false /glob/3.1.21: resolution: {integrity: sha512-ANhy2V2+tFpRajE3wN4DhkNQ08KDr0Ir1qL12/cUe5+a7STEK8jkW4onUYuY8/06qAFuT5je7mjAqzx0eKI2tQ==} @@ -12334,7 +12329,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: false /jake/10.8.2: resolution: {integrity: sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==} @@ -12972,7 +12966,6 @@ packages: /jiti/1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - dev: false /jotai-devtools/0.6.2_hgjqizhc26gc665cnflpub44vy: resolution: {integrity: sha512-iHKYt8V2T2Gh2DtGRpvpv2daVoFoHRJXqk5/LHnhFkJy9rMQuIGo4XgVu/v1ZMSvMzwDXdU3hDOQkfQWlDErUQ==} @@ -13477,12 +13470,10 @@ packages: /lilconfig/2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} - dev: false /lilconfig/3.1.2: resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} - dev: false /lines-and-columns/1.1.6: resolution: {integrity: sha512-8ZmlJFVK9iCmtLz19HpSsR8HaAMWBT284VMNednLwlIMDP2hJDCIhUp0IZ2xUcZ+Ob6BM0VvCSJwzASDM45NLQ==} @@ -14255,7 +14246,6 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - dev: false /nanoid/3.1.20: resolution: {integrity: sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==} @@ -14537,7 +14527,6 @@ packages: /object-hash/3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - dev: false /object-inspect/1.11.0: resolution: {integrity: sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==} @@ -14851,7 +14840,6 @@ packages: /package-json-from-dist/1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - dev: false /pako/0.2.9: resolution: {integrity: sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=} @@ -14997,6 +14985,7 @@ packages: /path-parse/1.0.6: resolution: {integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==} + dev: false /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -15065,11 +15054,6 @@ packages: /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /picomatch/2.3.0: - resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} - engines: {node: '>=8.6'} - dev: true - /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -15207,7 +15191,6 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.2 - dev: false /postcss-js/4.0.1_postcss@8.4.27: resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -15217,7 +15200,6 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.27 - dev: false /postcss-load-config/4.0.2_postcss@8.4.27: resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} @@ -15234,7 +15216,6 @@ packages: lilconfig: 3.1.2 postcss: 8.4.27 yaml: 2.5.1 - dev: false /postcss-merge-idents/2.1.7: resolution: {integrity: sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=} @@ -15342,7 +15323,6 @@ packages: dependencies: postcss: 8.4.27 postcss-selector-parser: 6.1.2 - dev: false /postcss-normalize-charset/1.1.1: resolution: {integrity: sha1-757nEhLX/nWceO0WL2HtYrXLk/E=} @@ -15401,7 +15381,6 @@ packages: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - dev: false /postcss-svgo/2.1.6: resolution: {integrity: sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=} @@ -15426,7 +15405,6 @@ packages: /postcss-value-parser/4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: false /postcss-zindex/2.2.0: resolution: {integrity: sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=} @@ -16997,7 +16975,6 @@ packages: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 - dev: false /read-only-stream/2.0.0: resolution: {integrity: sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=} @@ -17262,7 +17239,7 @@ packages: resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==} dependencies: is-core-module: 2.12.1 - path-parse: 1.0.6 + path-parse: 1.0.7 /resolve/1.22.2: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} @@ -18475,7 +18452,6 @@ packages: mz: 2.7.0 pirates: 4.0.6 ts-interface-checker: 0.1.13 - dev: false /superstruct/0.8.4: resolution: {integrity: sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==} @@ -18621,7 +18597,6 @@ packages: sucrase: 3.35.0 transitivePeerDependencies: - ts-node - dev: false /tapable/1.1.3: resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} @@ -18737,13 +18712,11 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 - dev: false /thenify/3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 - dev: false /three/0.139.2: resolution: {integrity: sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg==} @@ -18938,7 +18911,6 @@ packages: /ts-interface-checker/0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: false /ts-loader/5.3.3_typescript@5.5.4: resolution: {integrity: sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA==} @@ -20139,7 +20111,6 @@ packages: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} hasBin: true - dev: false /yargs-parser/20.2.4: resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} diff --git a/editor/src/core/property-controls/property-controls-local.ts b/editor/src/core/property-controls/property-controls-local.ts index c4a4beac7048..ffd05dff6e82 100644 --- a/editor/src/core/property-controls/property-controls-local.ts +++ b/editor/src/core/property-controls/property-controls-local.ts @@ -76,7 +76,7 @@ import { right, sequenceEither, } from '../shared/either' -import { assertNever } from '../shared/utils' +import { assertNever, identity } from '../shared/utils' import type { Imports, ParsedTextFile, @@ -157,73 +157,93 @@ function extendExportsWithInfo(exports: any, toImport: string): any { return exports } -export type ModuleEvaluator = (moduleName: string) => any -export function createModuleEvaluator(editor: EditorState): ModuleEvaluator { - return (moduleName: string) => { - let mutableContextRef: { current: MutableUtopiaCtxRefData } = { current: {} } - let topLevelComponentRendererComponents: { - current: MapLike> - } = { current: {} } - const emptyMetadataContext: UiJsxCanvasContextData = { - current: { - spyValues: { - allElementProps: {}, - metadata: {}, - variablesInScope: {}, - }, +export const createRequireFn = ( + editor: EditorState, + moduleName: string, + transform: (result: any, absoluteFilenameOrPackage: string) => any = identity, +) => { + let mutableContextRef: { current: MutableUtopiaCtxRefData } = { current: {} } + let topLevelComponentRendererComponents: { + current: MapLike> + } = { current: {} } + const emptyMetadataContext: UiJsxCanvasContextData = { + current: { + spyValues: { + allElementProps: {}, + metadata: {}, + variablesInScope: {}, }, - } + }, + } - let resolvedFiles: MapLike> = {} - let resolvedFileNames: Array = [moduleName] + let resolvedFiles: MapLike> = {} + let resolvedFileNames: Array = [moduleName] - const requireFn = editor.codeResultCache.curriedRequireFn(editor.projectContents) - const resolve = editor.codeResultCache.curriedResolveFn(editor.projectContents) + const requireFn = editor.codeResultCache.curriedRequireFn(editor.projectContents) + const resolve = editor.codeResultCache.curriedResolveFn(editor.projectContents) - const customRequire = (importOrigin: string, toImport: string) => { - if (resolvedFiles[importOrigin] == null) { - resolvedFiles[importOrigin] = [] - } - let resolvedFromThisOrigin = resolvedFiles[importOrigin] - - const alreadyResolved = resolvedFromThisOrigin[toImport] !== undefined - const filePathResolveResult = alreadyResolved - ? left('Already resolved') - : resolve(importOrigin, toImport) - - forEachRight(filePathResolveResult, (filepath) => resolvedFileNames.push(filepath)) - - const resolvedParseSuccess: Either> = attemptToResolveParsedComponents( - resolvedFromThisOrigin, - toImport, - editor.projectContents, - customRequire, - mutableContextRef, - topLevelComponentRendererComponents, - moduleName, - editor.canvas.base64Blobs, - editor.hiddenInstances, - editor.displayNoneInstances, - emptyMetadataContext, - NO_OP, - false, - filePathResolveResult, - null, - ) - const result = foldEither( - () => { - // We did not find a ParseSuccess, fallback to standard require Fn - return requireFn(importOrigin, toImport, false) - }, - (scope) => { - // Return an artificial exports object that contains our ComponentRendererComponents - return scope - }, - resolvedParseSuccess, - ) - const absoluteFilenameOrPackage = defaultEither(toImport, filePathResolveResult) - return extendExportsWithInfo(result, absoluteFilenameOrPackage) + const customRequire = (importOrigin: string, toImport: string) => { + if (resolvedFiles[importOrigin] == null) { + resolvedFiles[importOrigin] = [] } + let resolvedFromThisOrigin = resolvedFiles[importOrigin] + + const alreadyResolved = resolvedFromThisOrigin[toImport] !== undefined + const filePathResolveResult = alreadyResolved + ? left('Already resolved') + : resolve(importOrigin, toImport) + + forEachRight(filePathResolveResult, (filepath) => resolvedFileNames.push(filepath)) + + const resolvedParseSuccess: Either> = attemptToResolveParsedComponents( + resolvedFromThisOrigin, + toImport, + editor.projectContents, + customRequire, + mutableContextRef, + topLevelComponentRendererComponents, + moduleName, + editor.canvas.base64Blobs, + editor.hiddenInstances, + editor.displayNoneInstances, + emptyMetadataContext, + NO_OP, + false, + filePathResolveResult, + null, + ) + const result = foldEither( + () => { + // We did not find a ParseSuccess, fallback to standard require Fn + return requireFn(importOrigin, toImport, false) + }, + (scope) => { + // Return an artificial exports object that contains our ComponentRendererComponents + return scope + }, + resolvedParseSuccess, + ) + const absoluteFilenameOrPackage = defaultEither(toImport, filePathResolveResult) + return transform(result, absoluteFilenameOrPackage) + } + + return { + customRequire, + emptyMetadataContext, + mutableContextRef, + topLevelComponentRendererComponents, + } +} + +export type ModuleEvaluator = (moduleName: string) => any +export function createModuleEvaluator(editor: EditorState): ModuleEvaluator { + return (moduleName: string) => { + const { + customRequire, + emptyMetadataContext, + mutableContextRef, + topLevelComponentRendererComponents, + } = createRequireFn(editor, moduleName, extendExportsWithInfo) return createExecutionScope( moduleName, customRequire, diff --git a/editor/src/core/tailwind/tailwind-compilation.ts b/editor/src/core/tailwind/tailwind-compilation.ts index 9e030e1b83ad..78f5ef83eae0 100644 --- a/editor/src/core/tailwind/tailwind-compilation.ts +++ b/editor/src/core/tailwind/tailwind-compilation.ts @@ -1,7 +1,7 @@ import React from 'react' import type { TailwindConfig, Tailwindcss } from '@mhsdesign/jit-browser-tailwindcss' import { createTailwindcss } from '@mhsdesign/jit-browser-tailwindcss' -import type { ProjectContentTreeRoot } from 'utopia-shared/src/types' +import type { ProjectContentTreeRoot, TextFile, TextFileContents } from 'utopia-shared/src/types' import { getProjectFileByFilePath, walkContentsTree } from '../../components/assets' import { interactionSessionIsActive } from '../../components/canvas/canvas-strategies/interaction-state' import { CanvasContainerID } from '../../components/canvas/canvas-types' @@ -16,6 +16,31 @@ import type { RequireFn } from '../shared/npm-dependency-types' import { TailwindConfigPath } from './tailwind-config' import { ElementsToRerenderGLOBAL } from '../../components/canvas/ui-jsx-canvas' import { isFeatureEnabled } from '../../utils/feature-switches' +import type { Config } from 'tailwindcss/types/config' +import type { EditorState } from '../../components/editor/store/editor-state' +import { createRequireFn } from '../property-controls/property-controls-local' + +const LatestConfig: { current: { code: string; config: Config } | null } = { current: null } +export function getTailwindConfigCached(editorState: EditorState): Config | null { + const tailwindConfig = getProjectFileByFilePath(editorState.projectContents, TailwindConfigPath) + if (tailwindConfig == null || tailwindConfig.type !== 'TEXT_FILE') { + return null + } + const cached = + LatestConfig.current == null || LatestConfig.current.code !== tailwindConfig.fileContents.code + ? null + : LatestConfig.current.config + + if (cached != null) { + return cached + } + // FIXME this should use a shared long-lived require function instead of creating a brand new one + const { customRequire } = createRequireFn(editorState, TailwindConfigPath) + const config = importDefault(customRequire('/', TailwindConfigPath)) as Config + LatestConfig.current = { code: tailwindConfig.fileContents.code, config: config } + + return config +} const TAILWIND_INSTANCE: { current: Tailwindcss | null } = { current: null } diff --git a/editor/src/core/tailwind/tailwind.spec.browser2.tsx b/editor/src/core/tailwind/tailwind.spec.browser2.tsx index 8040db905fdc..b95956046c32 100644 --- a/editor/src/core/tailwind/tailwind.spec.browser2.tsx +++ b/editor/src/core/tailwind/tailwind.spec.browser2.tsx @@ -1,88 +1,6 @@ import { renderTestEditorWithModel } from '../../components/canvas/ui-jsx.test-utils' -import { createModifiedProject } from '../../sample-projects/sample-project-utils.test-utils' import { setFeatureForBrowserTestsUseInDescribeBlockOnly } from '../../utils/utils.test-utils' -import { wait } from '../model/performance-scripts' - -const Project = createModifiedProject({ - '/utopia/storyboard.js': `import { Scene, Storyboard } from 'utopia-api' - -export var storyboard = ( - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- Text Shadow - This is a medium text shadow example - This is a large text shadow example - This has no text shadow -
- - -) -`, - '/src/app.css': ` -@tailwind base; -@tailwind components; -@tailwind utilities; -`, - 'tailwind.config.js': ` -const Tailwind = { - theme: { - colors: { - transparent: 'transparent', - current: 'currentColor', - white: '#ffffff', - purple: '#3f3cbb', - midnight: '#121063', - metal: '#565584', - tahiti: '#3ab7bf', - silver: '#ecebff', - 'bubble-gum': '#ff77e9', - bermuda: '#78dcca', - }, - }, - plugins: [ - function ({ addUtilities }) { - const newUtilities = { - '.text-shadow': { - textShadow: '2px 2px 4px rgba(0, 0, 0, 0.1)', - }, - '.text-shadow-md': { - textShadow: '3px 3px 6px rgba(0, 0, 0, 0.2)', - }, - '.text-shadow-lg': { - textShadow: '4px 4px 8px rgba(0, 0, 0, 0.3)', - }, - '.text-shadow-none': { - textShadow: 'none', - }, - } - - addUtilities(newUtilities, ['responsive', 'hover']) - }, - ], -} -export default Tailwind -`, -}) +import { Project } from './tailwind.test-utils' describe('rendering tailwind projects in the editor', () => { setFeatureForBrowserTestsUseInDescribeBlockOnly('Tailwind', true) diff --git a/editor/src/core/tailwind/tailwind.spec.ts b/editor/src/core/tailwind/tailwind.spec.ts new file mode 100644 index 000000000000..dcaa26fb51c2 --- /dev/null +++ b/editor/src/core/tailwind/tailwind.spec.ts @@ -0,0 +1,72 @@ +import { Project, TailwindConfigFileContents } from './tailwind.test-utils' +import { renderTestEditorWithModel } from '../../components/canvas/ui-jsx.test-utils' +import { TailwindConfigPath } from './tailwind-config' +import { updateFromCodeEditor } from '../../components/editor/actions/actions-from-vscode' +import { getTailwindConfigCached } from './tailwind-compilation' + +describe('tailwind config file in the editor', () => { + it('is set during editor load', async () => { + const editor = await renderTestEditorWithModel(Project, 'await-first-dom-report') + + expect(getTailwindConfigCached(editor.getEditorState().editor)).toMatchInlineSnapshot(` + Object { + "plugins": Array [ + [Function], + ], + "theme": Object { + "colors": Object { + "bermuda": "#78dcca", + "bubble-gum": "#ff77e9", + "current": "currentColor", + "metal": "#565584", + "midnight": "#121063", + "purple": "#3f3cbb", + "silver": "#ecebff", + "tahiti": "#3ab7bf", + "transparent": "transparent", + "white": "#ffffff", + }, + }, + } + `) + }) + it('is updated in the editor state when the tailwind config is updated', async () => { + const editor = await renderTestEditorWithModel(Project, 'await-first-dom-report') + + await editor.dispatch( + [ + updateFromCodeEditor( + TailwindConfigPath, + TailwindConfigFileContents, + ` +const Tailwind = { + theme: { + colors: { + transparent: 'transparent', + current: 'currentColor', + white: '#ffffff', + }, + }, + plugins: [ ], + } + export default Tailwind +`, + ), + ], + true, + ) + + expect(getTailwindConfigCached(editor.getEditorState().editor)).toMatchInlineSnapshot(` + Object { + "plugins": Array [], + "theme": Object { + "colors": Object { + "current": "currentColor", + "transparent": "transparent", + "white": "#ffffff", + }, + }, + } + `) + }) +}) diff --git a/editor/src/core/tailwind/tailwind.test-utils.ts b/editor/src/core/tailwind/tailwind.test-utils.ts new file mode 100644 index 000000000000..80b5952ef0ab --- /dev/null +++ b/editor/src/core/tailwind/tailwind.test-utils.ts @@ -0,0 +1,85 @@ +import { createModifiedProject } from '../../sample-projects/sample-project-utils.test-utils' +import { TailwindConfigPath } from './tailwind-config' + +export const TailwindConfigFileContents = ` +const Tailwind = { + theme: { + colors: { + transparent: 'transparent', + current: 'currentColor', + white: '#ffffff', + purple: '#3f3cbb', + midnight: '#121063', + metal: '#565584', + tahiti: '#3ab7bf', + silver: '#ecebff', + 'bubble-gum': '#ff77e9', + bermuda: '#78dcca', + }, + }, + plugins: [ + function ({ addUtilities }) { + const newUtilities = { + '.text-shadow': { + textShadow: '2px 2px 4px rgba(0, 0, 0, 0.1)', + }, + '.text-shadow-md': { + textShadow: '3px 3px 6px rgba(0, 0, 0, 0.2)', + }, + '.text-shadow-lg': { + textShadow: '4px 4px 8px rgba(0, 0, 0, 0.3)', + }, + '.text-shadow-none': { + textShadow: 'none', + }, + } + + addUtilities(newUtilities, ['responsive', 'hover']) + }, + ], + } + export default Tailwind +` + +export const Project = createModifiedProject({ + '/utopia/storyboard.js': `import { Scene, Storyboard } from 'utopia-api' + + export var storyboard = ( + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Text Shadow + This is a medium text shadow example + This is a large text shadow example + This has no text shadow +
+ + + ) + `, + '/src/app.css': ` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + [TailwindConfigPath]: TailwindConfigFileContents, +}) diff --git a/editor/webpack.config.js b/editor/webpack.config.js index f0c7d9af5995..c236883873db 100644 --- a/editor/webpack.config.js +++ b/editor/webpack.config.js @@ -189,6 +189,7 @@ const config = { extensions: ['.ts', '.tsx', '.js', '.json', '.ttf'], symlinks: true, // We set this to false as we have symlinked some common code from the website project alias: { + 'tailwindcss/resolveConfig': 'tailwindcss/resolveConfig.js', uuiui: srcPath('uuiui'), 'worker-imports': path.resolve(__dirname, 'src/core/workers/worker-import-utils.ts'), 'uuiui-deps': srcPath('uuiui-deps'),