Browse Source

[Fix 477] Use jinja2 for templating (#915)

PeterYusuke 2 years ago
parent
commit
3b88e7c329

+ 83 - 5
poetry.lock

@@ -463,6 +463,84 @@ files = [
     {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
     {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
 ]
 ]
 
 
+[[package]]
+name = "jinja2"
+version = "3.1.2"
+description = "A very fast and expressive template engine."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+    {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.2"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
+    {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
+    {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
+    {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
+    {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
+    {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
+    {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
+]
+
 [[package]]
 [[package]]
 name = "mypy-extensions"
 name = "mypy-extensions"
 version = "1.0.0"
 version = "1.0.0"
@@ -1063,18 +1141,18 @@ files = [
 
 
 [[package]]
 [[package]]
 name = "redis"
 name = "redis"
-version = "4.5.4"
+version = "4.5.5"
 description = "Python client for Redis database and key-value store"
 description = "Python client for Redis database and key-value store"
 category = "main"
 category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
-    {file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"},
-    {file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"},
+    {file = "redis-4.5.5-py3-none-any.whl", hash = "sha256:77929bc7f5dab9adf3acba2d3bb7d7658f1e0c2f1cafe7eb36434e751c471119"},
+    {file = "redis-4.5.5.tar.gz", hash = "sha256:dc87a0bdef6c8bfe1ef1e1c40be7034390c2ae02d92dcd0c7ca1729443899880"},
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
-async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""}
+async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""}
 importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
 importlib-metadata = {version = ">=1.0", markers = "python_version < \"3.8\""}
 typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 
 
@@ -1600,4 +1678,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 [metadata]
 lock-version = "2.0"
 lock-version = "2.0"
 python-versions = "^3.7"
 python-versions = "^3.7"
-content-hash = "8c9f764a830657316f774fb679895c89d6874460582f4dbf8b2edbd4f534b262"
+content-hash = "43e53d9fff649b6a939ec953411b8932e512600b5af3edc0cbbbbb6c5576168b"

+ 19 - 17
pynecone/compiler/compiler.py

@@ -1,7 +1,6 @@
 """Compiler for the pynecone apps."""
 """Compiler for the pynecone apps."""
 from __future__ import annotations
 from __future__ import annotations
 
 
-import json
 from functools import wraps
 from functools import wraps
 from typing import Callable, List, Set, Tuple, Type
 from typing import Callable, List, Set, Tuple, Type
 
 
@@ -10,7 +9,7 @@ from pynecone.compiler import templates, utils
 from pynecone.components.component import Component, CustomComponent
 from pynecone.components.component import Component, CustomComponent
 from pynecone.state import State
 from pynecone.state import State
 from pynecone.style import Style
 from pynecone.style import Style
-from pynecone.utils import imports, path_ops
+from pynecone.utils import imports
 from pynecone.var import ImportVar
 from pynecone.var import ImportVar
 
 
 # Imports to be included in every Pynecone app.
 # Imports to be included in every Pynecone app.
@@ -42,7 +41,7 @@ def _compile_document_root(root: Component) -> str:
     Returns:
     Returns:
         The compiled document root.
         The compiled document root.
     """
     """
-    return templates.DOCUMENT_ROOT(
+    return templates.DOCUMENT_ROOT.render(
         imports=utils.compile_imports(root.get_imports()),
         imports=utils.compile_imports(root.get_imports()),
         document=root.render(),
         document=root.render(),
     )
     )
@@ -57,7 +56,7 @@ def _compile_theme(theme: dict) -> str:
     Returns:
     Returns:
         The compiled theme.
         The compiled theme.
     """
     """
-    return templates.THEME(theme=json.dumps(theme))
+    return templates.THEME.render(theme=theme)
 
 
 
 
 def _compile_page(component: Component, state: Type[State]) -> str:
 def _compile_page(component: Component, state: Type[State]) -> str:
@@ -72,17 +71,20 @@ def _compile_page(component: Component, state: Type[State]) -> str:
     """
     """
     # Merge the default imports with the app-specific imports.
     # Merge the default imports with the app-specific imports.
     imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
     imports = utils.merge_imports(DEFAULT_IMPORTS, component.get_imports())
+    imports = utils.compile_imports(imports)
 
 
     # Compile the code to render the component.
     # Compile the code to render the component.
-    return templates.PAGE(
-        imports=utils.compile_imports(imports),
-        custom_code=path_ops.join(component.get_custom_code()),
-        constants=utils.compile_constants(),
-        state=utils.compile_state(state),
-        events=utils.compile_events(state),
-        effects=utils.compile_effects(state),
-        hooks=path_ops.join(component.get_hooks()),
+    return templates.PAGE.render(
+        imports=imports,
+        custom_codes=component.get_custom_code(),
+        endpoints={
+            constant.name: constant.get_url() for constant in constants.Endpoint
+        },
+        initial_state=utils.compile_state(state),
+        state_name=state.get_name(),
+        hooks=component.get_hooks(),
         render=component.render(),
         render=component.render(),
+        transports=constants.Transports.POLLING_WEBSOCKET.get_transports(),
     )
     )
 
 
 
 
@@ -99,18 +101,18 @@ def _compile_components(components: Set[CustomComponent]) -> str:
         "react": {ImportVar(tag="memo")},
         "react": {ImportVar(tag="memo")},
         f"/{constants.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
         f"/{constants.STATE_PATH}": {ImportVar(tag="E"), ImportVar(tag="isTrue")},
     }
     }
-    component_defs = []
+    component_renders = []
 
 
     # Compile each component.
     # Compile each component.
     for component in components:
     for component in components:
-        component_def, component_imports = utils.compile_custom_component(component)
-        component_defs.append(component_def)
+        component_render, component_imports = utils.compile_custom_component(component)
+        component_renders.append(component_render)
         imports = utils.merge_imports(imports, component_imports)
         imports = utils.merge_imports(imports, component_imports)
 
 
     # Compile the components page.
     # Compile the components page.
-    return templates.COMPONENTS(
+    return templates.COMPONENTS.render(
         imports=utils.compile_imports(imports),
         imports=utils.compile_imports(imports),
-        components=path_ops.join(component_defs),
+        components=component_renders,
     )
     )
 
 
 
 

+ 51 - 188
pynecone/compiler/templates.py

@@ -1,163 +1,75 @@
 """Templates to use in the pynecone compiler."""
 """Templates to use in the pynecone compiler."""
 
 
-from typing import Optional, Set
+from jinja2 import Environment, FileSystemLoader, Template
 
 
 from pynecone import constants
 from pynecone import constants
 from pynecone.utils import path_ops
 from pynecone.utils import path_ops
-
-# Template for the Pynecone config file.
-PCCONFIG = f"""import pynecone as pc
-
-class {{config_name}}(pc.Config):
-    pass
-
-config = {{config_name}}(
-    app_name="{{app_name}}",
-    db_url="{constants.DB_URL}",
-    env=pc.Env.DEV,
-)
-"""
-
-# Javascript formatting.
-CONST = "const {name} = {value}".format
-PROP = "{object}.{property}".format
-IMPORT_LIB = 'import "{lib}"'.format
-IMPORT_FIELDS = 'import {default}{others} from "{lib}"'.format
-
-
-def format_import(lib: str, default: str = "", rest: Optional[Set[str]] = None) -> str:
-    """Format an import statement.
+from pynecone.utils.format import json_dumps
+
+
+class PyneconeJinjaEnvironment(Environment):
+    """The template class for jinja environment."""
+
+    def __init__(self) -> None:
+        """Set default environment."""
+        extensions = ["jinja2.ext.debug"]
+        super().__init__(
+            extensions=extensions,
+            trim_blocks=True,
+            lstrip_blocks=True,
+        )
+        self.filters["json_dumps"] = json_dumps
+        self.filters["react_setter"] = lambda state: f"set{state.capitalize()}"
+        self.loader = FileSystemLoader(constants.JINJA_TEMPLATE_DIR)
+        self.globals["const"] = {
+            "socket": constants.SOCKET,
+            "result": constants.RESULT,
+            "router": constants.ROUTER,
+            "event_endpoint": constants.Endpoint.EVENT.name,
+            "events": constants.EVENTS,
+            "state": constants.STATE,
+            "processing": constants.PROCESSING,
+            "initial_result": {
+                constants.STATE: None,
+                constants.EVENTS: [],
+                constants.PROCESSING: False,
+            },
+            "color_mode": constants.COLOR_MODE,
+            "toggle_color_mode": constants.TOGGLE_COLOR_MODE,
+            "use_color_mode": constants.USE_COLOR_MODE,
+        }
+
+
+def get_template(name: str) -> Template:
+    """Get render function that work with a template.
 
 
     Args:
     Args:
-        lib: The library to import from.
-        default: The default field to import.
-        rest: The set of fields to import from the library.
+        name: The template name. "/" is used as the path separator.
 
 
     Returns:
     Returns:
-        The compiled import statement.
+        A render function.
     """
     """
-    # Handle the case of direct imports with no libraries.
-    if not lib:
-        assert not default, "No default field allowed for empty library."
-        assert rest is not None and len(rest) > 0, "No fields to import."
-        return path_ops.join([IMPORT_LIB(lib=lib) for lib in sorted(rest)])
-
-    # Handle importing from a library.
-    rest = rest or set()
-    if len(default) == 0 and len(rest) == 0:
-        # Handle the case of importing a library with no fields.
-        return IMPORT_LIB(lib=lib)
-    # Handle importing specific fields from a library.
-    others = f'{{{", ".join(sorted(rest))}}}' if len(rest) > 0 else ""
-    if default != "" and len(rest) > 0:
-        default += ", "
-    return IMPORT_FIELDS(default=default, others=others, lib=lib)
+    return PyneconeJinjaEnvironment().get_template(name=name)
 
 
 
 
+# Template for the Pynecone config file.
+PCCONFIG = get_template("app/pcconfig.py.jinja2")
+
 # Code to render a NextJS Document root.
 # Code to render a NextJS Document root.
-DOCUMENT_ROOT = path_ops.join(
-    [
-        "{imports}",
-        "export default function Document() {{",
-        "return (",
-        "{document}",
-        ")",
-        "}}",
-    ]
-).format
+DOCUMENT_ROOT = get_template("web/pages/_document.js.jinja2")
 
 
 # Template for the theme file.
 # Template for the theme file.
-THEME = "export default {theme}".format
+THEME = get_template("web/utils/theme.js.jinja2")
 
 
 # Code to render a single NextJS page.
 # Code to render a single NextJS page.
-PAGE = path_ops.join(
-    [
-        "{imports}",
-        "{custom_code}",
-        "{constants}",
-        "export default function Component() {{",
-        "{state}",
-        "{events}",
-        "{effects}",
-        "{hooks}",
-        "return (",
-        "{render}",
-        ")",
-        "}}",
-    ]
-).format
-
-# Code to render a single exported custom component.
-COMPONENT = path_ops.join(
-    [
-        "export const {name} = memo(({{{props}}}) => (",
-        "{render}",
-        "))",
-    ]
-).format
+PAGE = get_template("web/pages/index.js.jinja2")
 
 
 # Code to render the custom components page.
 # Code to render the custom components page.
-COMPONENTS = path_ops.join(
-    [
-        "{imports}",
-        "{components}",
-    ]
-).format
-
-
-# React state declarations.
-USE_STATE = CONST(
-    name="[{state}, {set_state}]", value="useState({initial_state})"
-).format
-
-
-def format_state_setter(state: str) -> str:
-    """Format a state setter.
-
-    Args:
-        state: The name of the state variable.
-
-    Returns:
-        The compiled state setter.
-    """
-    return f"set{state[0].upper() + state[1:]}"
-
-
-def format_state(
-    state: str,
-    initial_state: str,
-) -> str:
-    """Format a state declaration.
-
-    Args:
-        state: The name of the state variable.
-        initial_state: The initial state of the state variable.
-
-    Returns:
-        The compiled state declaration.
-    """
-    set_state = format_state_setter(state)
-    return USE_STATE(state=state, set_state=set_state, initial_state=initial_state)
+COMPONENTS = get_template("web/pages/custom_component.js.jinja2")
 
 
+# Sitemap config file.
+SITEMAP_CONFIG = "module.exports = {config}".format
 
 
-# Events.
-EVENT_ENDPOINT = constants.Endpoint.EVENT.name
-EVENT_FN = path_ops.join(
-    [
-        "const Event = events => {set_state}({{",
-        "  ...{state},",
-        "  events: [...{state}.events, ...events],",
-        "}})",
-    ]
-).format
-UPLOAD_FN = path_ops.join(
-    [
-        "const File = files => {set_state}({{",
-        "  ...{state},",
-        "  files,",
-        "}})",
-    ]
-).format
 FULL_CONTROL = path_ops.join(
 FULL_CONTROL = path_ops.join(
     [
     [
         "{{setState(prev => ({{",
         "{{setState(prev => ({{",
@@ -167,52 +79,3 @@ FULL_CONTROL = path_ops.join(
         ")}}",
         ")}}",
     ]
     ]
 ).format
 ).format
-
-# Effects.
-ROUTER = constants.ROUTER
-RESULT = constants.RESULT
-PROCESSING = constants.PROCESSING
-SOCKET = constants.SOCKET
-STATE = constants.STATE
-EVENTS = constants.EVENTS
-SET_RESULT = format_state_setter(RESULT)
-READY = f"const {{ isReady }} = {ROUTER};"
-USE_EFFECT = path_ops.join(
-    [
-        "useEffect(() => {{",
-        "  if(!isReady) {{",
-        "    return;",
-        "  }}",
-        f"  if (!{SOCKET}.current) {{{{",
-        f"    connect({SOCKET}, {{state}}, {{set_state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {EVENT_ENDPOINT}, {{transports}})",
-        "  }}",
-        "  const update = async () => {{",
-        f"    if ({RESULT}.{STATE} != null) {{{{",
-        f"      {{set_state}}({{{{",
-        f"        ...{RESULT}.{STATE},",
-        f"        events: [...{{state}}.{EVENTS}, ...{RESULT}.{EVENTS}],",
-        "      }})",
-        f"      {SET_RESULT}({{{{",
-        f"        {STATE}: null,",
-        f"        {EVENTS}: [],",
-        f"        {PROCESSING}: false,",
-        "      }})",
-        "    }}",
-        f"    await updateState({{state}}, {{set_state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {SOCKET}.current)",
-        "  }}",
-        "  update()",
-        "}})",
-    ]
-).format
-
-# Routing
-ROUTER = f"const {constants.ROUTER} = useRouter()"
-
-# Sockets.
-SOCKET = "const socket = useRef(null)"
-
-# Color toggle
-COLORTOGGLE = f"const {{ {constants.COLOR_MODE}, {constants.TOGGLE_COLOR_MODE} }} = {constants.USE_COLOR_MODE}()"
-
-# Sitemap config file.
-SITEMAP_CONFIG = "module.exports = {config}".format

+ 41 - 106
pynecone/compiler/utils.py

@@ -1,11 +1,9 @@
 """Common utility functions used in the compiler."""
 """Common utility functions used in the compiler."""
 
 
-import json
 import os
 import os
 from typing import Dict, List, Optional, Set, Tuple, Type
 from typing import Dict, List, Optional, Set, Tuple, Type
 
 
 from pynecone import constants
 from pynecone import constants
-from pynecone.compiler import templates
 from pynecone.components.base import (
 from pynecone.components.base import (
     Body,
     Body,
     ColorModeScript,
     ColorModeScript,
@@ -31,15 +29,16 @@ from pynecone.var import ImportVar
 merge_imports = imports.merge_imports
 merge_imports = imports.merge_imports
 
 
 
 
-def compile_import_statement(lib: str, fields: Set[ImportVar]) -> str:
+def compile_import_statement(fields: Set[ImportVar]) -> Tuple[str, Set[str]]:
     """Compile an import statement.
     """Compile an import statement.
 
 
     Args:
     Args:
-        lib: The library to import from.
         fields: The set of fields to import from the library.
         fields: The set of fields to import from the library.
 
 
     Returns:
     Returns:
-        The compiled import statement.
+        The libraries for default and rest.
+        default: default library. When install "import def from library".
+        rest: rest of libraries. When install "import {rest1, rest2} from library"
     """
     """
     # Check for default imports.
     # Check for default imports.
     defaults = {field for field in fields if field.is_default}
     defaults = {field for field in fields if field.is_default}
@@ -48,58 +47,59 @@ def compile_import_statement(lib: str, fields: Set[ImportVar]) -> str:
     # Get the default import, and the specific imports.
     # Get the default import, and the specific imports.
     default = next(iter({field.name for field in defaults}), "")
     default = next(iter({field.name for field in defaults}), "")
     rest = {field.name for field in fields - defaults}
     rest = {field.name for field in fields - defaults}
-    return templates.format_import(lib=lib, default=default, rest=rest)
 
 
+    return default, rest
 
 
-def compile_imports(imports: imports.ImportDict) -> str:
+
+def compile_imports(imports: imports.ImportDict) -> List[dict]:
     """Compile an import dict.
     """Compile an import dict.
 
 
     Args:
     Args:
         imports: The import dict to compile.
         imports: The import dict to compile.
 
 
     Returns:
     Returns:
-        The compiled import dict.
+        The list of import dict.
     """
     """
-    return path_ops.join(
-        [compile_import_statement(lib, fields) for lib, fields in imports.items()]
-    )
-
+    import_dicts = []
+    for lib, fields in imports.items():
+        default, rest = compile_import_statement(fields)
+        if not lib:
+            assert not default, "No default field allowed for empty library."
+            assert rest is not None and len(rest) > 0, "No fields to import."
+            for module in sorted(rest):
+                import_dicts.append(get_import_dict(module))
+            continue
 
 
-def compile_constant_declaration(name: str, value: str) -> str:
-    """Compile a constant declaration.
-
-    Args:
-        name: The name of the constant.
-        value: The value of the constant.
+        import_dicts.append(get_import_dict(lib, default, rest))
+    return import_dicts
 
 
-    Returns:
-        The compiled constant declaration.
-    """
-    return templates.CONST(name=name, value=json.dumps(value))
 
 
+def get_import_dict(lib: str, default: str = "", rest: Optional[Set] = None) -> Dict:
+    """Get dictionary for import template.
 
 
-def compile_constants() -> str:
-    """Compile all the necessary constants.
+    Args:
+        lib: The importing react library.
+        default: The default module to import.
+        rest: The rest module to import.
 
 
     Returns:
     Returns:
-        A string of all the compiled constants.
+        A dictionary for import template.
     """
     """
-    return path_ops.join(
-        [
-            compile_constant_declaration(name=endpoint.name, value=endpoint.get_url())
-            for endpoint in constants.Endpoint
-        ]
-    )
+    return {
+        "lib": lib,
+        "default": default,
+        "rest": rest if rest else set(),
+    }
 
 
 
 
-def compile_state(state: Type[State]) -> str:
+def compile_state(state: Type[State]) -> Dict:
     """Compile the state of the app.
     """Compile the state of the app.
 
 
     Args:
     Args:
         state: The app state object.
         state: The app state object.
 
 
     Returns:
     Returns:
-        A string of the compiled state.
+        A dictionary of the compiled state.
     """
     """
     initial_state = state().dict()
     initial_state = state().dict()
     initial_state.update(
     initial_state.update(
@@ -108,77 +108,12 @@ def compile_state(state: Type[State]) -> str:
             "files": [],
             "files": [],
         }
         }
     )
     )
-    initial_state = format.format_state(initial_state)
-    synced_state = templates.format_state(
-        state=state.get_name(), initial_state=json.dumps(initial_state)
-    )
-    initial_result = {
-        constants.STATE: None,
-        constants.EVENTS: [],
-        constants.PROCESSING: False,
-    }
-    result = templates.format_state(
-        state="result",
-        initial_state=json.dumps(initial_result),
-    )
-    router = templates.ROUTER
-    socket = templates.SOCKET
-    ready = templates.READY
-    color_toggle = templates.COLORTOGGLE
-    return path_ops.join([synced_state, result, router, socket, ready, color_toggle])
-
-
-def compile_events(state: Type[State]) -> str:
-    """Compile all the events for a given component.
-
-    Args:
-        state: The state class for the component.
-
-    Returns:
-        A string of the compiled events for the component.
-    """
-    state_name = state.get_name()
-    state_setter = templates.format_state_setter(state_name)
-    return path_ops.join(
-        [
-            templates.EVENT_FN(state=state_name, set_state=state_setter),
-            templates.UPLOAD_FN(state=state_name, set_state=state_setter),
-        ]
-    )
-
-
-def compile_effects(state: Type[State]) -> str:
-    """Compile all the effects for a given component.
-
-    Args:
-        state: The state class for the component.
-
-    Returns:
-        A string of the compiled effects for the component.
-    """
-    state_name = state.get_name()
-    set_state = templates.format_state_setter(state_name)
-    transports = constants.Transports.POLLING_WEBSOCKET.get_transports()
-    return templates.USE_EFFECT(
-        state=state_name, set_state=set_state, transports=transports
-    )
-
-
-def compile_render(component: Component) -> str:
-    """Compile the component's render method.
-
-    Args:
-        component: The component to compile the render method for.
-
-    Returns:
-        A string of the compiled render method.
-    """
-    return component.render()
+    return format.format_state(initial_state)
 
 
 
 
 def compile_custom_component(
 def compile_custom_component(
     component: CustomComponent,
     component: CustomComponent,
-) -> Tuple[str, imports.ImportDict]:
+) -> Tuple[dict, imports.ImportDict]:
     """Compile a custom component.
     """Compile a custom component.
 
 
     Args:
     Args:
@@ -198,15 +133,15 @@ def compile_custom_component(
     }
     }
 
 
     # Concatenate the props.
     # Concatenate the props.
-    props = ", ".join([prop.name for prop in component.get_prop_vars()])
+    props = [prop.name for prop in component.get_prop_vars()]
 
 
     # Compile the component.
     # Compile the component.
     return (
     return (
-        templates.COMPONENT(
-            name=component.tag,
-            props=props,
-            render=render,
-        ),
+        {
+            "name": component.tag,
+            "props": props,
+            "render": render.render(),
+        },
         imports,
         imports,
     )
     )
 
 

+ 3 - 4
pynecone/components/base/meta.py

@@ -1,6 +1,6 @@
 """Display the title of the current page."""
 """Display the title of the current page."""
 
 
-from typing import Optional
+from typing import Dict, Optional
 
 
 from pynecone.components.base.bare import Bare
 from pynecone.components.base.bare import Bare
 from pynecone.components.component import Component
 from pynecone.components.component import Component
@@ -11,18 +11,17 @@ class Title(Component):
 
 
     tag = "title"
     tag = "title"
 
 
-    def render(self) -> str:
+    def render(self) -> Dict:
         """Render the title component.
         """Render the title component.
 
 
         Returns:
         Returns:
             The rendered title component.
             The rendered title component.
         """
         """
-        tag = self._render()
         # Make sure the title is a single string.
         # Make sure the title is a single string.
         assert len(self.children) == 1 and isinstance(
         assert len(self.children) == 1 and isinstance(
             self.children[0], Bare
             self.children[0], Bare
         ), "Title must be a single string."
         ), "Title must be a single string."
-        return str(tag.set(contents=str(self.children[0].contents)))
+        return super().render()
 
 
 
 
 class Meta(Component):
 class Meta(Component):

+ 10 - 10
pynecone/components/component.py

@@ -21,7 +21,7 @@ from pynecone.event import (
     get_handler_args,
     get_handler_args,
 )
 )
 from pynecone.style import Style
 from pynecone.style import Style
-from pynecone.utils import format, imports, path_ops, types
+from pynecone.utils import format, imports, types
 from pynecone.var import BaseVar, ImportVar, Var
 from pynecone.var import BaseVar, ImportVar, Var
 
 
 
 
@@ -289,7 +289,7 @@ class Component(Base, ABC):
         Returns:
         Returns:
             The code to render the component.
             The code to render the component.
         """
         """
-        return self.render()
+        return format.json_dumps(self.render())
 
 
     def __str__(self) -> str:
     def __str__(self) -> str:
         """Represent the component in React.
         """Represent the component in React.
@@ -297,7 +297,7 @@ class Component(Base, ABC):
         Returns:
         Returns:
             The code to render the component.
             The code to render the component.
         """
         """
-        return self.render()
+        return format.json_dumps(self.render())
 
 
     def _render(self) -> Tag:
     def _render(self) -> Tag:
         """Define how to render the component in React.
         """Define how to render the component in React.
@@ -393,14 +393,14 @@ class Component(Base, ABC):
             child.add_style(style)
             child.add_style(style)
         return self
         return self
 
 
-    def render(self) -> str:
+    def render(self) -> Dict:
         """Render the component.
         """Render the component.
 
 
         Returns:
         Returns:
-            The code to render the component.
+            The dictionary for template of component.
         """
         """
         tag = self._render()
         tag = self._render()
-        return str(
+        return dict(
             tag.add_props(
             tag.add_props(
                 **self.event_triggers,
                 **self.event_triggers,
                 key=self.key,
                 key=self.key,
@@ -408,10 +408,10 @@ class Component(Base, ABC):
                 id=self.id,
                 id=self.id,
                 class_name=self.class_name,
                 class_name=self.class_name,
             ).set(
             ).set(
-                contents=path_ops.join(
-                    [str(tag.contents)] + [child.render() for child in self.children]
-                ).strip(),
-            )
+                children=[child.render() for child in self.children],
+                contents=str(tag.contents),
+                props=tag.format_props(),
+            ),
         )
         )
 
 
     def _get_custom_code(self) -> Optional[str]:
     def _get_custom_code(self) -> Optional[str]:

+ 26 - 4
pynecone/components/layout/cond.py

@@ -1,7 +1,7 @@
 """Create a list of components from an iterable."""
 """Create a list of components from an iterable."""
 from __future__ import annotations
 from __future__ import annotations
 
 
-from typing import Any, Optional
+from typing import Any, Dict, Optional
 
 
 from pynecone.components.component import Component
 from pynecone.components.component import Component
 from pynecone.components.layout.fragment import Fragment
 from pynecone.components.layout.fragment import Fragment
@@ -24,7 +24,7 @@ class Cond(Component):
 
 
     @classmethod
     @classmethod
     def create(
     def create(
-        cls, cond: Var, comp1: Component, comp2: Optional[Component] = None
+        cls, cond: Var, comp1: Component, comp2: Optional[Component]
     ) -> Component:
     ) -> Component:
         """Create a conditional component.
         """Create a conditional component.
 
 
@@ -37,8 +37,10 @@ class Cond(Component):
             The conditional component.
             The conditional component.
         """
         """
         # Wrap everything in fragments.
         # Wrap everything in fragments.
-        comp1 = Fragment.create(comp1)
-        comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
+        if comp1.__class__.__name__ != "Fragment":
+            comp1 = Fragment.create(comp1)
+        if comp2 is None or comp2.__class__.__name__ != "Fragment":
+            comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
         return Fragment.create(
         return Fragment.create(
             cls(
             cls(
                 cond=cond,
                 cond=cond,
@@ -55,6 +57,26 @@ class Cond(Component):
             false_value=self.comp2.render(),
             false_value=self.comp2.render(),
         )
         )
 
 
+    def render(self) -> Dict:
+        """Render the component.
+
+        Returns:
+            The dictionary for template of component.
+        """
+        tag = self._render()
+        return dict(
+            tag.add_props(
+                **self.event_triggers,
+                key=self.key,
+                sx=self.style,
+                id=self.id,
+                class_name=self.class_name,
+            ).set(
+                props=tag.format_props(),
+            ),
+            cond_state=f"isTrue({self.cond.full_name})",
+        )
+
 
 
 def cond(condition: Any, c1: Any, c2: Any = None):
 def cond(condition: Any, c1: Any, c2: Any = None):
     """Create a conditional component or Prop.
     """Create a conditional component or Prop.

+ 36 - 3
pynecone/components/layout/foreach.py

@@ -4,8 +4,8 @@ from __future__ import annotations
 from typing import Any, Callable, List
 from typing import Any, Callable, List
 
 
 from pynecone.components.component import Component
 from pynecone.components.component import Component
-from pynecone.components.tags import IterTag, Tag
-from pynecone.var import BaseVar, Var
+from pynecone.components.tags import IterTag
+from pynecone.var import BaseVar, Var, get_unique_variable_name
 
 
 
 
 class Foreach(Component):
 class Foreach(Component):
@@ -49,5 +49,38 @@ class Foreach(Component):
             **props,
             **props,
         )
         )
 
 
-    def _render(self) -> Tag:
+    def _render(self) -> IterTag:
         return IterTag(iterable=self.iterable, render_fn=self.render_fn)
         return IterTag(iterable=self.iterable, render_fn=self.render_fn)
+
+    def render(self):
+        """Render the component.
+
+        Returns:
+            The dictionary for template of component.
+        """
+        tag = self._render()
+        try:
+            type_ = self.iterable.type_.__args__[0]
+        except Exception:
+            type_ = Any
+        arg = BaseVar(
+            name=get_unique_variable_name(),
+            type_=type_,
+        )
+        index_arg = tag.get_index_var_arg()
+        component = tag.render_component(self.render_fn, arg)
+        return dict(
+            tag.add_props(
+                **self.event_triggers,
+                key=self.key,
+                sx=self.style,
+                id=self.id,
+                class_name=self.class_name,
+            ).set(
+                children=[component.render()],
+                props=tag.format_props(),
+            ),
+            iterable_state=tag.iterable.full_name,
+            arg_name=arg.name,
+            arg_index=index_arg,
+        )

+ 3 - 17
pynecone/components/tags/cond_tag.py

@@ -1,9 +1,8 @@
 """Tag to conditionally render components."""
 """Tag to conditionally render components."""
 
 
-from typing import Any
+from typing import Any, Dict, Optional
 
 
 from pynecone.components.tags.tag import Tag
 from pynecone.components.tags.tag import Tag
-from pynecone.utils import format
 from pynecone.var import Var
 from pynecone.var import Var
 
 
 
 
@@ -14,20 +13,7 @@ class CondTag(Tag):
     cond: Var[Any]
     cond: Var[Any]
 
 
     # The code to render if the condition is true.
     # The code to render if the condition is true.
-    true_value: str
+    true_value: Dict
 
 
     # The code to render if the condition is false.
     # The code to render if the condition is false.
-    false_value: str
-
-    def __str__(self) -> str:
-        """Render the tag as a React string.
-
-        Returns:
-            The React code to render the tag.
-        """
-        assert self.cond is not None, "The condition must be set."
-        return format.format_cond(
-            cond=self.cond.full_name,
-            true_value=self.true_value,
-            false_value=self.false_value,
-        )
+    false_value: Optional[Dict]

+ 2 - 24
pynecone/components/tags/iter_tag.py

@@ -2,11 +2,10 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import inspect
 import inspect
-from typing import TYPE_CHECKING, Any, Callable, List
+from typing import TYPE_CHECKING, Callable, List
 
 
 from pynecone.components.tags.tag import Tag
 from pynecone.components.tags.tag import Tag
-from pynecone.utils import format
-from pynecone.var import BaseVar, Var, get_unique_variable_name
+from pynecone.var import Var
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from pynecone.components.component import Component
     from pynecone.components.component import Component
@@ -83,24 +82,3 @@ class IterTag(Tag):
             component.key = index
             component.key = index
 
 
         return component
         return component
-
-    def __str__(self) -> str:
-        """Render the tag as a React string.
-
-        Returns:
-            The React code to render the tag.
-        """
-        try:
-            type_ = self.iterable.type_.__args__[0]
-        except Exception:
-            type_ = Any
-        arg = BaseVar(
-            name=get_unique_variable_name(),
-            type_=type_,
-        )
-        index_arg = self.get_index_var_arg()
-        component = self.render_component(self.render_fn, arg)
-        return format.wrap(
-            f"{self.iterable.full_name}.map(({arg.name}, {index_arg}) => {component})",
-            "{",
-        )

+ 9 - 40
pynecone/components/tags/tag.py

@@ -3,9 +3,8 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import json
 import json
-import os
 import re
 import re
-from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Tuple, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
 
 
 from plotly.graph_objects import Figure
 from plotly.graph_objects import Figure
 from plotly.io import to_json
 from plotly.io import to_json
@@ -37,6 +36,9 @@ class Tag(Base):
     # Special props that aren't key value pairs.
     # Special props that aren't key value pairs.
     special_props: Set[Var] = set()
     special_props: Set[Var] = set()
 
 
+    # The children components.
+    children: List[Any] = []
+
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         """Initialize the tag.
         """Initialize the tag.
 
 
@@ -117,55 +119,22 @@ class Tag(Base):
         assert isinstance(prop, str), "The prop must be a string."
         assert isinstance(prop, str), "The prop must be a string."
         return format.wrap(prop, "{", check_first=False)
         return format.wrap(prop, "{", check_first=False)
 
 
-    def format_props(self) -> str:
+    def format_props(self) -> List:
         """Format the tag's props.
         """Format the tag's props.
 
 
         Returns:
         Returns:
-            The formatted props.
+            The formatted props list.
         """
         """
         # If there are no props, return an empty string.
         # If there are no props, return an empty string.
         if len(self.props) == 0:
         if len(self.props) == 0:
-            return ""
+            return []
 
 
         # Format all the props.
         # Format all the props.
-        return os.linesep.join(
+        return [
             f"{name}={self.format_prop(prop)}"
             f"{name}={self.format_prop(prop)}"
             for name, prop in sorted(self.props.items())
             for name, prop in sorted(self.props.items())
             if prop is not None
             if prop is not None
-        )
-
-    def __str__(self) -> str:
-        """Render the tag as a React string.
-
-        Returns:
-            The React code to render the tag.
-        """
-        # Get the tag props.
-        props_str = self.format_props()
-
-        # Add the special props.
-        props_str += " ".join([str(prop) for prop in self.special_props])
-
-        # Add a space if there are props.
-        if len(props_str) > 0:
-            props_str = " " + props_str
-
-        if len(self.contents) == 0:
-            # If there is no inner content, we don't need a closing tag.
-            tag_str = format.wrap(f"{self.name}{props_str}/", "<")
-        else:
-            if self.args is not None:
-                # If there are args, wrap the tag in a function call.
-                args_str = ", ".join(self.args)
-                contents = f"{{({{{args_str}}}) => ({self.contents})}}"
-            else:
-                contents = self.contents
-            # Otherwise wrap it in opening and closing tags.
-            open = format.wrap(f"{self.name}{props_str}", "<")
-            close = format.wrap(f"/{self.name}", "<")
-            tag_str = format.wrap(contents, open, close)
-
-        return tag_str
+        ] + [str(prop) for prop in self.special_props]
 
 
     def add_props(self, **kwargs: Optional[Any]) -> Tag:
     def add_props(self, **kwargs: Optional[Any]) -> Tag:
         """Add props to the tag.
         """Add props to the tag.

+ 2 - 0
pynecone/constants.py

@@ -33,6 +33,8 @@ TEMPLATE_DIR = os.path.join(ROOT_DIR, MODULE_NAME, ".templates")
 WEB_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "web")
 WEB_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "web")
 # The assets subdirectory of the template directory.
 # The assets subdirectory of the template directory.
 ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
 ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
+# The jinja template directory.
+JINJA_TEMPLATE_DIR = os.path.join(ROOT_DIR, MODULE_NAME, "templates")
 
 
 # The frontend directories in a project.
 # The frontend directories in a project.
 # The web folder where the NextJS app is compiled to.
 # The web folder where the NextJS app is compiled to.

+ 6 - 6
pynecone/el/element.py

@@ -1,7 +1,8 @@
 """Base class definition for raw HTML elements."""
 """Base class definition for raw HTML elements."""
 
 
+from typing import Dict
+
 from pynecone.components.component import Component
 from pynecone.components.component import Component
-from pynecone.utils import path_ops
 
 
 
 
 class Element(Component):
 class Element(Component):
@@ -12,14 +13,14 @@ class Element(Component):
     prop.
     prop.
     """
     """
 
 
-    def render(self) -> str:
+    def render(self) -> Dict:
         """Render the element.
         """Render the element.
 
 
         Returns:
         Returns:
             The code to render the element.
             The code to render the element.
         """
         """
         tag = self._render()
         tag = self._render()
-        return str(
+        return dict(
             tag.add_props(
             tag.add_props(
                 **self.event_triggers,
                 **self.event_triggers,
                 key=self.key,
                 key=self.key,
@@ -27,9 +28,8 @@ class Element(Component):
                 style=self.style,
                 style=self.style,
                 class_name=self.class_name,
                 class_name=self.class_name,
             ).set(
             ).set(
-                contents=path_ops.join(
-                    [str(tag.contents)] + [child.render() for child in self.children]
-                ).strip(),
+                contents=str(tag.contents),
+                children=[child.render() for child in self.children],
             )
             )
         )
         )
 
 

+ 10 - 0
pynecone/templates/app/pcconfig.py.jinja2

@@ -0,0 +1,10 @@
+import pynecone as pc
+
+class {{ config_name }}(pc.Config):
+    pass
+
+config = {{ config_name }}(
+    app_name="{{ app_name }}",
+    db_url="{{ db_url }}",
+    env=pc.Env.DEV,
+)

+ 9 - 0
pynecone/templates/web/pages/_document.js.jinja2

@@ -0,0 +1,9 @@
+{% extends "web/pages/base_page.js.jinja2" %}
+
+{% block export %}
+export default function Document() {
+  return (
+    {{utils.render(document, indent_width=4)}}
+  )
+}
+{% endblock %}

+ 13 - 0
pynecone/templates/web/pages/base_page.js.jinja2

@@ -0,0 +1,13 @@
+{% import 'web/pages/utils.js.jinja2' as utils %}
+
+{%- block imports_libs %}
+{% for module in imports%}
+  {{- utils.get_import(module) }}
+{% endfor %}
+{% endblock %}
+
+{% block declaration %}
+{% endblock %}
+
+{% block export %}
+{% endblock %}

+ 10 - 0
pynecone/templates/web/pages/custom_component.js.jinja2

@@ -0,0 +1,10 @@
+{% extends "web/pages/base_page.js.jinja2" %}
+
+{% block export %}
+{% for component in components %}
+
+export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => (
+  {{utils.render(component.render)}}
+))
+{% endfor %}
+{% endblock %}

+ 66 - 0
pynecone/templates/web/pages/index.js.jinja2

@@ -0,0 +1,66 @@
+{% extends "web/pages/base_page.js.jinja2" %}
+
+{% block declaration %}
+{% for custom_code in custom_codes %}
+{{custom_code}}
+{% endfor %}
+
+{% for name, url in endpoints.items() %}
+const {{name}} = {{url|json_dumps}}
+{% endfor %}
+{% endblock %}
+
+{% block export %}
+export default function Component() {
+  const [{{state_name}}, {{state_name|react_setter}}] = useState({{initial_state|json_dumps}})
+  const [{{const.result}}, {{const.result|react_setter}}] = useState({{const.initial_result|json_dumps}})
+  const {{const.router}} = useRouter()
+  const {{const.socket}} = useRef(null)
+  const { isReady } = {{const.router}}
+  const { {{const.color_mode}}, {{const.toggle_color_mode}} } = {{const.use_color_mode}}()
+
+  const Event = events => {{state_name|react_setter}}({
+    ...{{state_name}},
+    events: [...{{state_name}}.events, ...events],
+  })
+
+  const File = files => {{state_name|react_setter}}({
+    ...{{state_name}},
+    files,
+  })
+
+  useEffect(()=>{
+    if(!isReady) {
+      return;
+    }
+    if (!{{const.socket}}.current) {
+      connect({{const.socket}}, {{state_name}}, {{state_name|react_setter}}, {{const.result}}, {{const.result|react_setter}}, {{const.router}}, {{const.event_endpoint}}, {{transports}})
+    }
+    const update = async () => {
+      if ({{const.result}}.{{const.state}} != null){
+        {{state_name|react_setter}}({
+          ...{{const.result}}.{{const.state}},
+          events: [...{{state_name}}.{{const.events}}, ...{{const.result}}.{{const.events}}],
+        })
+
+        {{const.result|react_setter}}({
+          {{const.state}}: null,
+          {{const.events}}: [],
+          {{const.processing}}: false,
+        })
+      }
+
+      await updateState({{state_name}}, {{state_name|react_setter}}, {{const.result}}, {{const.result|react_setter}}, {{const.router}}, {{const.socket}}.current)
+      }
+      update()
+  })
+
+  {% for hook in hooks %}
+  {{ hook }}
+  {% endfor %}
+
+  return (
+    {{utils.render(render, indent_width=4)}}
+  )
+}
+{% endblock %}

+ 110 - 0
pynecone/templates/web/pages/utils.js.jinja2

@@ -0,0 +1,110 @@
+{# Renderting components recursively. #}
+{# Args: #}
+{#     component: component dictionary #}
+{#     indent_width: indent width #}
+{% macro render(component, indent_width=2) %}
+{% filter indent(width=indent_width) %}
+  {%- if component is not mapping %}
+    {{- component }}
+  {%- elif component.iterable %}
+    {{- render_iterable_tag(component) }}
+  {%- elif component.cond %}
+    {{- render_condition_tag(component) }}
+  {%- elif component.children|length %}
+    {{- render_tag(component) }}
+  {%- else %}
+    {{- render_self_close_tag(component) }}
+  {%- endif %}
+{% endfilter %}
+{% endmacro %}
+
+{# Renderting self close tag. #}
+{# Args: #}
+{#     component: component dictionary #}
+{% macro render_self_close_tag(component) %}
+{%- if component.name|length %}
+<{{ component.name }} {{- render_props(component.props) }}/>
+{%- else %}
+  {{- component.contents }}
+{%- endif %}
+{% endmacro %}
+
+{# Renderting close tag with args and props. #}
+{# Args: #}
+{#     component: component dictionary #}
+{% macro render_tag(component) %}
+<{{component.name}} {{- render_props(component.props) }}>
+{%- if component.args is not none -%}
+  {{- render_arg_content(component) }}
+{%- else -%}
+  {{ component.contents }}
+  {% for child in component.children %}
+  {{ render(child) }}
+  {% endfor %}
+{%- endif -%}
+</{{component.name}}>
+{%- endmacro %}
+
+
+{# Renderting condition component. #}
+{# Args: #}
+{#     component: component dictionary #}
+{% macro render_condition_tag(component) %}
+{ {{- component.cond_state }} ? (
+  {{ render(component.true_value) }}
+) : (
+  {{ render(component.false_value) }}
+)}
+{%- endmacro %}
+
+
+{# Renderting iterable component. #}
+{# Args: #}
+{#     component: component dictionary #}
+{% macro render_iterable_tag(component) %}
+{ {{- component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
+  {% for child in component.children %}
+  {{ render(child) }}
+  {% endfor %}
+))}
+{%- endmacro %}
+
+
+{# Renderting props of a component. #}
+{# Args: #}
+{#     component: component dictionary #}
+{% macro render_props(props) %}
+{% if props|length %} {{ props|join(" ") }}{% endif %}
+{% endmacro %}
+
+
+{# Renderting content with args. #}
+{# Args: #}
+{#     component: component dictionary #}
+{% macro render_arg_content(component) %}
+{% filter indent(width=2) %}
+{# no string below for a line break #}
+
+{({ {{component.args|join(", ")}} }) => (
+  {% for child in component.children %}
+  {{ render(child) }}
+  {% endfor %}
+)}
+{% endfilter %}
+{% endmacro %}
+
+
+{# Get react libraries import . #}
+{# Args: #}
+{#     module: react module dictionary #}
+{% macro get_import(module)%}
+{%- if module.default|length and module.rest|length -%}
+  import {{module.default}}, { {{module.rest|sort|join(", ")}} } from "{{module.lib}}"
+{%- elif module.default|length -%}
+  import {{module.default}} from "{{module.lib}}"
+{%- elif module.rest|length -%}
+  import { {{module.rest|sort|join(", ")}} } from "{{module.lib}}"
+{%- else -%}
+  import "{{module.lib}}"
+{%- endif -%}
+{% endmacro %}

+ 1 - 0
pynecone/templates/web/utils/theme.js.jinja2

@@ -0,0 +1 @@
+export default {{ theme|json_dumps }}

+ 1 - 3
pynecone/utils/format.py

@@ -306,11 +306,9 @@ def format_upload_event(event_spec: EventSpec) -> str:
     Returns:
     Returns:
         The compiled event.
         The compiled event.
     """
     """
-    from pynecone.compiler import templates
-
     state, name = get_event_handler_parts(event_spec.handler)
     state, name = get_event_handler_parts(event_spec.handler)
     parent_state = state.split(".")[0]
     parent_state = state.split(".")[0]
-    return f'uploadFiles({parent_state}, {templates.RESULT}, {templates.SET_RESULT}, {parent_state}.files, "{state}.{name}",UPLOAD)'
+    return f'uploadFiles({parent_state}, {constants.RESULT}, set{constants.RESULT.capitalize()}, {parent_state}.files, "{state}.{name}",UPLOAD)'
 
 
 
 
 def format_full_control_event(event_chain: EventChain) -> str:
 def format_full_control_event(event_chain: EventChain) -> str:

+ 1 - 1
pynecone/utils/prerequisites.py

@@ -151,7 +151,7 @@ def create_config(app_name: str):
 
 
     config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
     config_name = f"{re.sub(r'[^a-zA-Z]', '', app_name).capitalize()}Config"
     with open(constants.CONFIG_FILE, "w") as f:
     with open(constants.CONFIG_FILE, "w") as f:
-        f.write(templates.PCCONFIG.format(app_name=app_name, config_name=config_name))
+        f.write(templates.PCCONFIG.render(app_name=app_name, config_name=config_name))
 
 
 
 
 def create_web_directory(root: Path) -> str:
 def create_web_directory(root: Path) -> str:

+ 1 - 0
pyproject.toml

@@ -40,6 +40,7 @@ websockets = "^10.4"
 cloudpickle = "^2.2.1"
 cloudpickle = "^2.2.1"
 python-multipart = "^0.0.5"
 python-multipart = "^0.0.5"
 watchdog = "^2.3.1"
 watchdog = "^2.3.1"
+jinja2 = "^3.1.2"
 
 
 [tool.poetry.group.dev.dependencies]
 [tool.poetry.group.dev.dependencies]
 pytest = "^7.1.2"
 pytest = "^7.1.2"

+ 58 - 45
tests/compiler/test_compiler.py

@@ -1,4 +1,4 @@
-from typing import Set
+from typing import List, Set
 
 
 import pytest
 import pytest
 
 
@@ -8,51 +8,55 @@ from pynecone.var import ImportVar
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
-    "lib,fields,output",
+    "fields,test_default,test_rest",
     [
     [
         (
         (
-            "axios",
             {ImportVar(tag="axios", is_default=True)},
             {ImportVar(tag="axios", is_default=True)},
-            'import axios from "axios"',
+            "axios",
+            set(),
         ),
         ),
         (
         (
-            "axios",
             {ImportVar(tag="foo"), ImportVar(tag="bar")},
             {ImportVar(tag="foo"), ImportVar(tag="bar")},
-            'import {bar, foo} from "axios"',
+            "",
+            {"foo", "bar"},
         ),
         ),
         (
         (
-            "axios",
             {
             {
                 ImportVar(tag="axios", is_default=True),
                 ImportVar(tag="axios", is_default=True),
                 ImportVar(tag="foo"),
                 ImportVar(tag="foo"),
                 ImportVar(tag="bar"),
                 ImportVar(tag="bar"),
             },
             },
-            "import " "axios, " "{bar, " "foo} from " '"axios"',
+            "axios",
+            {"foo", "bar"},
         ),
         ),
     ],
     ],
 )
 )
-def test_compile_import_statement(lib: str, fields: Set[ImportVar], output: str):
+def test_compile_import_statement(
+    fields: Set[ImportVar], test_default: str, test_rest: str
+):
     """Test the compile_import_statement function.
     """Test the compile_import_statement function.
 
 
     Args:
     Args:
-        lib: The library name.
         fields: The fields to import.
         fields: The fields to import.
-        output: The expected output.
+        test_default: The expected output of default library.
+        test_rest: The expected output rest libraries.
     """
     """
-    assert utils.compile_import_statement(lib, fields) == output
+    default, rest = utils.compile_import_statement(fields)
+    assert default == test_default
+    assert rest == test_rest
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
-    "import_dict,output",
+    "import_dict,test_dicts",
     [
     [
-        ({}, ""),
+        ({}, []),
         (
         (
             {"axios": {ImportVar(tag="axios", is_default=True)}},
             {"axios": {ImportVar(tag="axios", is_default=True)}},
-            'import axios from "axios"',
+            [{"lib": "axios", "default": "axios", "rest": set()}],
         ),
         ),
         (
         (
             {"axios": {ImportVar(tag="foo"), ImportVar(tag="bar")}},
             {"axios": {ImportVar(tag="foo"), ImportVar(tag="bar")}},
-            'import {bar, foo} from "axios"',
+            [{"lib": "axios", "default": "", "rest": {"foo", "bar"}}],
         ),
         ),
         (
         (
             {
             {
@@ -63,52 +67,61 @@ def test_compile_import_statement(lib: str, fields: Set[ImportVar], output: str)
                 },
                 },
                 "react": {ImportVar(tag="react", is_default=True)},
                 "react": {ImportVar(tag="react", is_default=True)},
             },
             },
-            'import axios, {bar, foo} from "axios"\nimport react from "react"',
+            [
+                {"lib": "axios", "default": "axios", "rest": {"foo", "bar"}},
+                {"lib": "react", "default": "react", "rest": set()},
+            ],
         ),
         ),
         (
         (
             {"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")}},
             {"": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")}},
-            'import "lib1.js"\nimport "lib2.js"',
+            [
+                {"lib": "lib1.js", "default": "", "rest": set()},
+                {"lib": "lib2.js", "default": "", "rest": set()},
+            ],
         ),
         ),
         (
         (
             {
             {
                 "": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")},
                 "": {ImportVar(tag="lib1.js"), ImportVar(tag="lib2.js")},
                 "axios": {ImportVar(tag="axios", is_default=True)},
                 "axios": {ImportVar(tag="axios", is_default=True)},
             },
             },
-            'import "lib1.js"\nimport "lib2.js"\nimport axios from "axios"',
+            [
+                {"lib": "lib1.js", "default": "", "rest": set()},
+                {"lib": "lib2.js", "default": "", "rest": set()},
+                {"lib": "axios", "default": "axios", "rest": set()},
+            ],
         ),
         ),
     ],
     ],
 )
 )
-def test_compile_imports(
-    import_dict: imports.ImportDict, output: str, windows_platform: bool
-):
+def test_compile_imports(import_dict: imports.ImportDict, test_dicts: List[dict]):
     """Test the compile_imports function.
     """Test the compile_imports function.
 
 
     Args:
     Args:
         import_dict: The import dictionary.
         import_dict: The import dictionary.
-        output: The expected output.
-        windows_platform: whether system is windows.
+        test_dicts: The expected output.
     """
     """
-    assert utils.compile_imports(import_dict) == (
-        output.replace("\n", "\r\n") if windows_platform else output
-    )
+    imports = utils.compile_imports(import_dict)
+    for import_dict, test_dict in zip(imports, test_dicts):
+        assert import_dict["lib"] == test_dict["lib"]
+        assert import_dict["default"] == test_dict["default"]
+        assert import_dict["rest"] == test_dict["rest"]
 
 
 
 
-@pytest.mark.parametrize(
-    "name,value,output",
-    [
-        ("foo", "bar", 'const foo = "bar"'),
-        ("num", 1, "const num = 1"),
-        ("check", False, "const check = false"),
-        ("arr", [1, 2, 3], "const arr = [1, 2, 3]"),
-        ("obj", {"foo": "bar"}, 'const obj = {"foo": "bar"}'),
-    ],
-)
-def test_compile_constant_declaration(name: str, value: str, output: str):
-    """Test the compile_constant_declaration function.
+# @pytest.mark.parametrize(
+#     "name,value,output",
+#     [
+#         ("foo", "bar", 'const foo = "bar"'),
+#         ("num", 1, "const num = 1"),
+#         ("check", False, "const check = false"),
+#         ("arr", [1, 2, 3], "const arr = [1, 2, 3]"),
+#         ("obj", {"foo": "bar"}, 'const obj = {"foo": "bar"}'),
+#     ],
+# )
+# def test_compile_constant_declaration(name: str, value: str, output: str):
+#     """Test the compile_constant_declaration function.
 
 
-    Args:
-        name: The name of the constant.
-        value: The value of the constant.
-        output: The expected output.
-    """
-    assert utils.compile_constant_declaration(name, value) == output
+#     Args:
+#         name: The name of the constant.
+#         value: The value of the constant.
+#         output: The expected output.
+#     """
+#     assert utils.compile_constant_declaration(name, value) == output

+ 2 - 2
tests/components/base/test_bare.py

@@ -19,5 +19,5 @@ def test_fstrings(contents, expected):
         contents: The contents of the component.
         contents: The contents of the component.
         expected: The expected output.
         expected: The expected output.
     """
     """
-    comp = Bare.create(contents)
-    assert str(comp) == expected
+    comp = Bare.create(contents).render()
+    assert comp["contents"] == expected

+ 6 - 7
tests/components/datadisplay/test_datatable.py

@@ -1,5 +1,3 @@
-import os
-
 import pandas as pd
 import pandas as pd
 import pytest
 import pytest
 
 
@@ -37,11 +35,12 @@ def test_validate_data_table(data_table_state: pc.Var, expected):
         props["columns"] = data_table_state.columns
         props["columns"] = data_table_state.columns
     data_table_component = data_table(**props)
     data_table_component = data_table(**props)
 
 
-    assert (
-        str(data_table_component)
-        == f"<DataTableGrid columns={{{expected}.columns}}{os.linesep}data={{"
-        f"{expected}.data}}/>"
-    )
+    data_table_dict = data_table_component.render()
+
+    assert data_table_dict["props"] == [
+        f"columns={{{expected}.columns}}",
+        f"data={{{expected}.data}}",
+    ]
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(

+ 37 - 20
tests/components/forms/test_uploads.py

@@ -1,5 +1,3 @@
-import os
-
 import pytest
 import pytest
 
 
 import pynecone as pc
 import pynecone as pc
@@ -49,14 +47,36 @@ def test_upload_component_render(upload_component):
     Args:
     Args:
         upload_component: component fixture
         upload_component: component fixture
     """
     """
+    uplaod = upload_component.render()
+
+    # upload
+    assert uplaod["name"] == "ReactDropzone"
+    assert uplaod["props"] == [
+        "multiple={true}",
+        "onDrop={e => File(e)}",
+    ]
+    assert uplaod["args"] == ("getRootProps", "getInputProps")
+
+    # box inside of upload
+    [box] = uplaod["children"]
+    assert box["name"] == "Box"
+    assert box["props"] == [
+        'sx={{"border": "1px dotted black"}}',
+        "{...getRootProps()}",
+    ]
+
+    # input, button and text inside of box
+    [input, button, text] = box["children"]
+    assert input["name"] == "Input"
+    assert input["props"] == ['type="file"', "{...getInputProps()}"]
+
+    assert button["name"] == "Button"
+    assert button["children"][0]["contents"] == "{`select file`}"
+
+    assert text["name"] == "Text"
     assert (
     assert (
-        str(upload_component) == f"<ReactDropzone multiple={{true}}{os.linesep}"
-        "onDrop={e => File(e)}>{({getRootProps, getInputProps}) => (<Box "
-        'sx={{"border": "1px dotted black"}}{...getRootProps()}><Input '
-        f'type="file"{{...getInputProps()}}/>{os.linesep}'
-        f"<Button>{{`select file`}}</Button>{os.linesep}"
-        "<Text>{`Drag and drop files here or click to select "
-        "files`}</Text></Box>)}</ReactDropzone>"
+        text["children"][0]["contents"]
+        == "{`Drag and drop files here or click to select files`}"
     )
     )
 
 
 
 
@@ -66,14 +86,11 @@ def test_upload_component_with_props_render(upload_component_with_props):
     Args:
     Args:
         upload_component_with_props: component fixture
         upload_component_with_props: component fixture
     """
     """
-    assert (
-        str(upload_component_with_props) == f"<ReactDropzone maxFiles={{2}}{os.linesep}"
-        f"multiple={{true}}{os.linesep}"
-        f"noDrag={{true}}{os.linesep}"
-        "onDrop={e => File(e)}>{({getRootProps, getInputProps}) => (<Box "
-        'sx={{"border": "1px dotted black"}}{...getRootProps()}><Input '
-        f'type="file"{{...getInputProps()}}/>{os.linesep}'
-        f"<Button>{{`select file`}}</Button>{os.linesep}"
-        "<Text>{`Drag and drop files here or click to select "
-        "files`}</Text></Box>)}</ReactDropzone>"
-    )
+    uplaod = upload_component_with_props.render()
+
+    assert uplaod["props"] == [
+        "maxFiles={2}",
+        "multiple={true}",
+        "noDrag={true}",
+        "onDrop={e => File(e)}",
+    ]

+ 20 - 5
tests/components/layout/test_cond.py

@@ -38,12 +38,27 @@ def test_validate_cond(cond_state: pc.Var):
         Text.create("cond is True"),
         Text.create("cond is True"),
         Text.create("cond is False"),
         Text.create("cond is False"),
     )
     )
+    cond_dict = cond_component.render() if type(cond_component) == Fragment else {}
+    assert cond_dict["name"] == "Fragment"
 
 
-    assert str(cond_component) == (
-        "<Fragment>{isTrue(cond_state.value) ? "
-        "<Fragment><Text>{`cond is True`}</Text></Fragment> : "
-        "<Fragment><Text>{`cond is False`}</Text></Fragment>}</Fragment>"
-    )
+    [condition] = cond_dict["children"]
+    assert condition["cond_state"] == "isTrue(cond_state.value)"
+
+    # true value
+    true_value = condition["true_value"]
+    assert true_value["name"] == "Fragment"
+
+    [true_value_text] = true_value["children"]
+    assert true_value_text["name"] == "Text"
+    assert true_value_text["children"][0]["contents"] == "{`cond is True`}"
+
+    # false value
+    false_value = condition["false_value"]
+    assert false_value["name"] == "Fragment"
+
+    [false_value_text] = false_value["children"]
+    assert false_value_text["name"] == "Text"
+    assert false_value_text["children"][0]["contents"] == "{`cond is False`}"
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(

+ 50 - 30
tests/components/test_tag.py

@@ -1,4 +1,4 @@
-from typing import Any, Dict
+from typing import Any, Dict, List
 
 
 import pytest
 import pytest
 
 
@@ -71,25 +71,24 @@ def test_format_prop(prop: Var, formatted: str):
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
-    "props,formatted",
+    "props,test_props",
     [
     [
-        ({}, ""),
-        ({"key": 1}, "key={1}"),
-        ({"key": "value"}, 'key="value"'),
-        ({"key": True, "key2": "value2"}, 'key={true}\nkey2="value2"'),
+        ({}, []),
+        ({"key": 1}, ["key={1}"]),
+        ({"key": "value"}, ['key="value"']),
+        ({"key": True, "key2": "value2"}, ["key={true}", 'key2="value2"']),
     ],
     ],
 )
 )
-def test_format_props(props: Dict[str, Var], formatted: str, windows_platform: bool):
+def test_format_props(props: Dict[str, Var], test_props: List):
     """Test that the formatted props are correct.
     """Test that the formatted props are correct.
 
 
     Args:
     Args:
         props: The props to test.
         props: The props to test.
-        formatted: The expected formatted props.
-        windows_platform: Whether the system is windows.
+        test_props: The expected props.
     """
     """
-    assert Tag(props=props).format_props() == (
-        formatted.replace("\n", "\r\n") if windows_platform else formatted
-    )
+    tag_props = Tag(props=props).format_props()
+    for i, tag_prop in enumerate(tag_props):
+        assert tag_prop == test_props[i]
 
 
 
 
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
@@ -126,13 +125,20 @@ def test_add_props():
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
     "tag,expected",
     "tag,expected",
     [
     [
-        (Tag(), "</>"),
-        (Tag(name="br"), "<br/>"),
-        (Tag(contents="hello"), "<>hello</>"),
-        (Tag(name="h1", contents="hello"), "<h1>hello</h1>"),
+        (Tag(), {"name": "", "contents": "", "props": {}}),
+        (Tag(name="br"), {"name": "br", "contents": "", "props": {}}),
+        (Tag(contents="hello"), {"name": "", "contents": "hello", "props": {}}),
+        (
+            Tag(name="h1", contents="hello"),
+            {"name": "h1", "contents": "hello", "props": {}},
+        ),
         (
         (
             Tag(name="box", props={"color": "red", "textAlign": "center"}),
             Tag(name="box", props={"color": "red", "textAlign": "center"}),
-            '<box color="red"\ntextAlign="center"/>',
+            {
+                "name": "box",
+                "contents": "",
+                "props": {"color": "red", "textAlign": "center"},
+            },
         ),
         ),
         (
         (
             Tag(
             Tag(
@@ -140,30 +146,44 @@ def test_add_props():
                 props={"color": "red", "textAlign": "center"},
                 props={"color": "red", "textAlign": "center"},
                 contents="text",
                 contents="text",
             ),
             ),
-            '<box color="red"\ntextAlign="center">text</box>',
+            {
+                "name": "box",
+                "contents": "text",
+                "props": {"color": "red", "textAlign": "center"},
+            },
         ),
         ),
     ],
     ],
 )
 )
-def test_format_tag(tag: Tag, expected: str, windows_platform: bool):
-    """Test that the formatted tag is correct.
+def test_format_tag(tag: Tag, expected: Dict):
+    """Test that the tag dict is correct.
 
 
     Args:
     Args:
         tag: The tag to test.
         tag: The tag to test.
-        expected: The expected formatted tag.
-        windows_platform: Whether the system is windows.
+        expected: The expected tag dictionary.
     """
     """
-    expected = expected.replace("\n", "\r\n") if windows_platform else expected
-    assert str(tag) == expected
+    tag_dict = dict(tag)
+    assert tag_dict["name"] == expected["name"]
+    assert tag_dict["contents"] == expected["contents"]
+    assert tag_dict["props"] == expected["props"]
 
 
 
 
 def test_format_cond_tag():
 def test_format_cond_tag():
-    """Test that the formatted cond tag is correct."""
+    """Test that the cond tag dict is correct."""
     tag = CondTag(
     tag = CondTag(
-        true_value=str(Tag(name="h1", contents="True content")),
-        false_value=str(Tag(name="h2", contents="False content")),
+        true_value=dict(Tag(name="h1", contents="True content")),
+        false_value=dict(Tag(name="h2", contents="False content")),
         cond=BaseVar(name="logged_in", type_=bool),
         cond=BaseVar(name="logged_in", type_=bool),
     )
     )
-    assert (
-        str(tag)
-        == "{isTrue(logged_in) ? <h1>True content</h1> : <h2>False content</h2>}"
+    tag_dict = dict(tag)
+    cond, true_value, false_value = (
+        tag_dict["cond"],
+        tag_dict["true_value"],
+        tag_dict["false_value"],
     )
     )
+    assert cond == "logged_in"
+
+    assert true_value["name"] == "h1"
+    assert true_value["contents"] == "True content"
+
+    assert false_value["name"] == "h2"
+    assert false_value["contents"] == "False content"

+ 1 - 1
tests/test_utils.py

@@ -335,7 +335,7 @@ def test_create_config(app_name, expected_config_name, mocker):
     mocker.patch("builtins.open")
     mocker.patch("builtins.open")
     tmpl_mock = mocker.patch("pynecone.compiler.templates.PCCONFIG")
     tmpl_mock = mocker.patch("pynecone.compiler.templates.PCCONFIG")
     prerequisites.create_config(app_name)
     prerequisites.create_config(app_name)
-    tmpl_mock.format.assert_called_with(
+    tmpl_mock.render.assert_called_with(
         app_name=app_name, config_name=expected_config_name
         app_name=app_name, config_name=expected_config_name
     )
     )