diff --git a/pynecone/.templates/jinja/web/tailwind.config.js.jinja2 b/pynecone/.templates/jinja/web/tailwind.config.js.jinja2 new file mode 100644 index 000000000..afe9ecfa0 --- /dev/null +++ b/pynecone/.templates/jinja/web/tailwind.config.js.jinja2 @@ -0,0 +1,17 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: {{content|json_dumps}}, + theme: {{theme|json_dumps}}, + plugins: [ + {% for plugin in plugins %} + require({{plugin|json_dumps}}), + {% endfor %} + ], + {% if presets is defined %} + presets: [ + {% for preset in presets %} + require({{preset|json_dumps}}) + {% endfor %} + ] + {% endif %} +}; diff --git a/pynecone/.templates/web/bun.lockb b/pynecone/.templates/web/bun.lockb index c5a22c3e0..a1152504c 100755 Binary files a/pynecone/.templates/web/bun.lockb and b/pynecone/.templates/web/bun.lockb differ diff --git a/pynecone/.templates/web/package.json b/pynecone/.templates/web/package.json index 03a569a65..eaa7d3173 100644 --- a/pynecone/.templates/web/package.json +++ b/pynecone/.templates/web/package.json @@ -34,5 +34,10 @@ "remark-math": "^5.1.1", "socket.io-client": "^4.6.1", "victory": "^36.6.8" + }, + "devDependencies": { + "autoprefixer": "^10.4.14", + "postcss": "^8.4.24", + "tailwindcss": "^3.3.2" } } diff --git a/pynecone/.templates/web/pages/_app.js b/pynecone/.templates/web/pages/_app.js index 22687ac01..71f1776e7 100644 --- a/pynecone/.templates/web/pages/_app.js +++ b/pynecone/.templates/web/pages/_app.js @@ -2,6 +2,8 @@ import { ChakraProvider, extendTheme } from "@chakra-ui/react"; import { Global, css } from "@emotion/react"; import theme from "/utils/theme"; +import '../styles/tailwind.css' + const GlobalStyles = css` /* Hide the blue border around Chakra components. */ .js-focus-visible :focus:not([data-focus-visible-added]) { diff --git a/pynecone/.templates/web/postcss.config.js b/pynecone/.templates/web/postcss.config.js new file mode 100644 index 000000000..33ad091d2 --- /dev/null +++ b/pynecone/.templates/web/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/pynecone/.templates/web/styles/tailwind.css b/pynecone/.templates/web/styles/tailwind.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/pynecone/.templates/web/styles/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/pynecone/app.py b/pynecone/app.py index 922fa2480..df0aa9566 100644 --- a/pynecone/app.py +++ b/pynecone/app.py @@ -453,6 +453,13 @@ class App(Base): # Compile the theme. compiler.compile_theme(self.style) + # Compile the Tailwind config. + compiler.compile_tailwind( + dict(**config.tailwind, content=constants.TAILWIND_CONTENT) + if config.tailwind is not None + else {} + ) + # Compile the pages. custom_components = set() thread_pool = ThreadPool() diff --git a/pynecone/compiler/compiler.py b/pynecone/compiler/compiler.py index 250307487..b456f5339 100644 --- a/pynecone/compiler/compiler.py +++ b/pynecone/compiler/compiler.py @@ -125,6 +125,22 @@ def _compile_components(components: Set[CustomComponent]) -> str: ) +def _compile_tailwind( + config: dict, +) -> str: + """Compile the Tailwind config. + + Args: + config: The Tailwind config. + + Returns: + The compiled Tailwind config. + """ + return templates.TAILWIND_CONFIG.render( + **config, + ) + + def write_output(fn: Callable[..., Tuple[str, str]]): """Write the output of the function to a file. @@ -239,6 +255,26 @@ def compile_components(components: Set[CustomComponent]): return output_path, code +@write_output +def compile_tailwind( + config: dict, +): + """Compile the Tailwind config. + + Args: + config: The Tailwind config. + + Returns: + The compiled Tailwind config. + """ + # Get the path for the output file. + output_path = constants.TAILWIND_CONFIG + + # Compile the config. + code = _compile_tailwind(config) + return output_path, code + + def purge_web_pages_dir(): """Empty out .web directory.""" template_files = ["_app.js", "404.js"] diff --git a/pynecone/compiler/templates.py b/pynecone/compiler/templates.py index c336aa258..beae4f88a 100644 --- a/pynecone/compiler/templates.py +++ b/pynecone/compiler/templates.py @@ -65,6 +65,9 @@ DOCUMENT_ROOT = get_template("web/pages/_document.js.jinja2") # Template for the theme file. THEME = get_template("web/utils/theme.js.jinja2") +# Template for Tailwind config. +TAILWIND_CONFIG = get_template("web/tailwind.config.js.jinja2") + # Code to render a single NextJS page. PAGE = get_template("web/pages/index.js.jinja2") diff --git a/pynecone/config.py b/pynecone/config.py index 67889ac98..a44acbe71 100644 --- a/pynecone/config.py +++ b/pynecone/config.py @@ -6,7 +6,7 @@ import importlib import os import sys import urllib.parse -from typing import List, Optional +from typing import Any, Dict, List, Optional from dotenv import load_dotenv @@ -197,6 +197,9 @@ class Config(Base): # Whether to override OS environment variables. override_os_envs: Optional[bool] = True + # Tailwind config. + tailwind: Optional[Dict[str, Any]] = None + # Timeout when launching the gunicorn server. timeout: int = constants.TIMEOUT diff --git a/pynecone/constants.py b/pynecone/constants.py index 36681a20f..bf38acb39 100644 --- a/pynecone/constants.py +++ b/pynecone/constants.py @@ -77,6 +77,10 @@ WEB_STATIC_DIR = os.path.join(WEB_DIR, "_static") WEB_UTILS_DIR = os.path.join(WEB_DIR, UTILS_DIR) # The directory where the assets are located. WEB_ASSETS_DIR = os.path.join(WEB_DIR, "public") +# The Tailwind config. +TAILWIND_CONFIG = os.path.join(WEB_DIR, "tailwind.config.js") +# Default Tailwind content paths +TAILWIND_CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"] # The sitemap config file. SITEMAP_CONFIG_FILE = os.path.join(WEB_DIR, "next-sitemap.config.js") # The node modules directory.