Memoize markdown component_map (#2219)
This commit is contained in:
parent
065b1b88d2
commit
626357ed87
@ -3,6 +3,10 @@
|
|||||||
{% block export %}
|
{% block export %}
|
||||||
{% for component in components %}
|
{% for component in components %}
|
||||||
|
|
||||||
|
{% for custom_code in component.custom_code %}
|
||||||
|
{{custom_code}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => {
|
export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => {
|
||||||
{% if component.name == "CodeBlock" and "language" in component.props %}
|
{% if component.name == "CodeBlock" and "language" in component.props %}
|
||||||
if (language) {
|
if (language) {
|
||||||
|
@ -254,6 +254,7 @@ def compile_custom_component(
|
|||||||
"name": component.tag,
|
"name": component.tag,
|
||||||
"props": props,
|
"props": props,
|
||||||
"render": render.render(),
|
"render": render.render(),
|
||||||
|
"custom_code": render.get_custom_code(),
|
||||||
},
|
},
|
||||||
imports,
|
imports,
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""A code component."""
|
"""A code component."""
|
||||||
|
import re
|
||||||
from typing import Dict, Literal, Optional, Union
|
from typing import Dict, Literal, Optional, Union
|
||||||
|
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component
|
||||||
@ -362,6 +362,9 @@ class CodeBlock(Component):
|
|||||||
# The language to use.
|
# The language to use.
|
||||||
language: Var[LiteralCodeLanguage] = "python" # type: ignore
|
language: Var[LiteralCodeLanguage] = "python" # type: ignore
|
||||||
|
|
||||||
|
# The code to display.
|
||||||
|
code: Var[str]
|
||||||
|
|
||||||
# If this is enabled line numbers will be shown next to the code block.
|
# If this is enabled line numbers will be shown next to the code block.
|
||||||
show_line_numbers: Var[bool]
|
show_line_numbers: Var[bool]
|
||||||
|
|
||||||
@ -379,16 +382,21 @@ class CodeBlock(Component):
|
|||||||
|
|
||||||
def _get_imports(self) -> imports.ImportDict:
|
def _get_imports(self) -> imports.ImportDict:
|
||||||
merged_imports = super()._get_imports()
|
merged_imports = super()._get_imports()
|
||||||
|
# Get all themes from a cond literal
|
||||||
|
themes = re.findall(r"`(.*?)`", self.theme._var_name)
|
||||||
|
if not themes:
|
||||||
|
themes = [self.theme._var_name]
|
||||||
merged_imports = imports.merge_imports(
|
merged_imports = imports.merge_imports(
|
||||||
merged_imports,
|
merged_imports,
|
||||||
{
|
{
|
||||||
f"react-syntax-highlighter/dist/cjs/styles/prism/{self.theme._var_name}": {
|
f"react-syntax-highlighter/dist/cjs/styles/prism/{theme}": {
|
||||||
ImportVar(
|
ImportVar(
|
||||||
tag=format.to_camel_case(self.theme._var_name),
|
tag=format.to_camel_case(theme),
|
||||||
is_default=True,
|
is_default=True,
|
||||||
install=False,
|
install=False,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
for theme in themes
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
@ -440,7 +448,7 @@ class CodeBlock(Component):
|
|||||||
|
|
||||||
# react-syntax-highlighter doesnt have an explicit "light" or "dark" theme so we use one-light and one-dark
|
# react-syntax-highlighter doesnt have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||||
# themes respectively to ensure code compatibility.
|
# themes respectively to ensure code compatibility.
|
||||||
if "theme" in props:
|
if "theme" in props and not isinstance(props["theme"], Var):
|
||||||
props["theme"] = (
|
props["theme"] = (
|
||||||
"one-light"
|
"one-light"
|
||||||
if props["theme"] == "light"
|
if props["theme"] == "light"
|
||||||
@ -469,9 +477,14 @@ class CodeBlock(Component):
|
|||||||
if key not in cls.get_fields():
|
if key not in cls.get_fields():
|
||||||
custom_style[key] = value
|
custom_style[key] = value
|
||||||
|
|
||||||
|
# Carry the children (code) via props
|
||||||
|
if children:
|
||||||
|
props["code"] = children[0]
|
||||||
|
if not isinstance(props["code"], Var):
|
||||||
|
props["code"] = Var.create(props["code"], _var_is_string=True)
|
||||||
|
|
||||||
# Create the component.
|
# Create the component.
|
||||||
code_block = super().create(
|
code_block = super().create(
|
||||||
*children,
|
|
||||||
**props,
|
**props,
|
||||||
custom_style=Style(custom_style),
|
custom_style=Style(custom_style),
|
||||||
)
|
)
|
||||||
@ -486,11 +499,15 @@ class CodeBlock(Component):
|
|||||||
|
|
||||||
def _render(self):
|
def _render(self):
|
||||||
out = super()._render()
|
out = super()._render()
|
||||||
|
predicate, qmark, value = self.theme._var_name.partition("?")
|
||||||
out.add_props(
|
out.add_props(
|
||||||
style=Var.create(
|
style=Var.create(
|
||||||
format.to_camel_case(self.theme._var_name), _var_is_local=False
|
format.to_camel_case(f"{predicate}{qmark}{value.replace('`', '')}"),
|
||||||
|
_var_is_local=False,
|
||||||
)
|
)
|
||||||
).remove_props("theme")
|
).remove_props("theme", "code")
|
||||||
|
if self.code is not None:
|
||||||
|
out.special_props.add(Var.create_safe(f"children={str(self.code)}"))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from typing import Any, Dict, Literal, Optional, Union, overload
|
|||||||
from reflex.vars import Var, BaseVar, ComputedVar
|
from reflex.vars import Var, BaseVar, ComputedVar
|
||||||
from reflex.event import EventChain, EventHandler, EventSpec
|
from reflex.event import EventChain, EventHandler, EventSpec
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
|
import re
|
||||||
from typing import Dict, Literal, Optional, Union
|
from typing import Dict, Literal, Optional, Union
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component
|
||||||
from reflex.components.forms import Button
|
from reflex.components.forms import Button
|
||||||
@ -1024,6 +1025,7 @@ class CodeBlock(Component):
|
|||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
|
code: Optional[Union[Var[str], str]] = None,
|
||||||
show_line_numbers: Optional[Union[Var[bool], bool]] = None,
|
show_line_numbers: Optional[Union[Var[bool], bool]] = None,
|
||||||
starting_line_number: Optional[Union[Var[int], int]] = None,
|
starting_line_number: Optional[Union[Var[int], int]] = None,
|
||||||
wrap_long_lines: Optional[Union[Var[bool], bool]] = None,
|
wrap_long_lines: Optional[Union[Var[bool], bool]] = None,
|
||||||
@ -1090,6 +1092,7 @@ class CodeBlock(Component):
|
|||||||
copy_button: A custom copy button to override the default one.
|
copy_button: A custom copy button to override the default one.
|
||||||
theme: The theme to use ("light" or "dark").
|
theme: The theme to use ("light" or "dark").
|
||||||
language: The language to use.
|
language: The language to use.
|
||||||
|
code: The code to display.
|
||||||
show_line_numbers: If this is enabled line numbers will be shown next to the code block.
|
show_line_numbers: If this is enabled line numbers will be shown next to the code block.
|
||||||
starting_line_number: The starting line number to use.
|
starting_line_number: The starting line number to use.
|
||||||
wrap_long_lines: Whether to wrap long lines.
|
wrap_long_lines: Whether to wrap long lines.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from hashlib import md5
|
||||||
from typing import Any, Callable, Dict, Union
|
from typing import Any, Callable, Dict, Union
|
||||||
|
|
||||||
from reflex.compiler import utils
|
from reflex.compiler import utils
|
||||||
@ -67,8 +68,8 @@ def get_base_component_map() -> dict[str, Callable]:
|
|||||||
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
||||||
"a": lambda value: Link.create(value),
|
"a": lambda value: Link.create(value),
|
||||||
"code": lambda value: Code.create(value),
|
"code": lambda value: Code.create(value),
|
||||||
"codeblock": lambda *_, **props: CodeBlock.create(
|
"codeblock": lambda value, **props: CodeBlock.create(
|
||||||
theme="light", margin_y="1em", **props
|
value, theme="light", margin_y="1em", **props
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,14 +233,14 @@ class Markdown(Component):
|
|||||||
The formatted component map.
|
The formatted component map.
|
||||||
"""
|
"""
|
||||||
components = {
|
components = {
|
||||||
tag: f"{{({{{_CHILDREN._var_name}, {_PROPS._var_name}}}) => {self.format_component(tag)}}}"
|
tag: f"{{({{node, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => {self.format_component(tag)}}}"
|
||||||
for tag in self.component_map
|
for tag in self.component_map
|
||||||
}
|
}
|
||||||
|
|
||||||
# Separate out inline code and code blocks.
|
# Separate out inline code and code blocks.
|
||||||
components[
|
components[
|
||||||
"code"
|
"code"
|
||||||
] = f"""{{({{inline, className, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => {{
|
] = f"""{{({{node, inline, className, {_CHILDREN._var_name}, {_PROPS._var_name}}}) => {{
|
||||||
const match = (className || '').match(/language-(?<lang>.*)/);
|
const match = (className || '').match(/language-(?<lang>.*)/);
|
||||||
const language = match ? match[1] : '';
|
const language = match ? match[1] : '';
|
||||||
if (language) {{
|
if (language) {{
|
||||||
@ -255,7 +256,7 @@ class Markdown(Component):
|
|||||||
return inline ? (
|
return inline ? (
|
||||||
{self.format_component("code")}
|
{self.format_component("code")}
|
||||||
) : (
|
) : (
|
||||||
{self.format_component("codeblock", language=Var.create_safe("language", _var_is_local=False), children=Var.create_safe("String(children)", _var_is_local=False))}
|
{self.format_component("codeblock", language=Var.create_safe("language", _var_is_local=False))}
|
||||||
);
|
);
|
||||||
}}}}""".replace(
|
}}}}""".replace(
|
||||||
"\n", " "
|
"\n", " "
|
||||||
@ -263,14 +264,41 @@ class Markdown(Component):
|
|||||||
|
|
||||||
return components
|
return components
|
||||||
|
|
||||||
|
def _component_map_hash(self) -> str:
|
||||||
|
return md5(str(self.component_map).encode()).hexdigest()
|
||||||
|
|
||||||
|
def _get_component_map_name(self) -> str:
|
||||||
|
return f"ComponentMap_{self._component_map_hash()}"
|
||||||
|
|
||||||
|
def _get_custom_code(self) -> str | None:
|
||||||
|
hooks = set()
|
||||||
|
for component in self.component_map.values():
|
||||||
|
hooks |= component(_MOCK_ARG).get_hooks()
|
||||||
|
formatted_hooks = "\n".join(hooks)
|
||||||
|
return f"""
|
||||||
|
function {self._get_component_map_name()} () {{
|
||||||
|
{formatted_hooks}
|
||||||
|
return (
|
||||||
|
{str(Var.create(self.format_component_map()))}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
return (
|
tag = (
|
||||||
super()
|
super()
|
||||||
._render()
|
._render()
|
||||||
.add_props(
|
.add_props(
|
||||||
components=self.format_component_map(),
|
|
||||||
remark_plugins=_REMARK_PLUGINS,
|
remark_plugins=_REMARK_PLUGINS,
|
||||||
rehype_plugins=_REHYPE_PLUGINS,
|
rehype_plugins=_REHYPE_PLUGINS,
|
||||||
)
|
)
|
||||||
.remove_props("componentMap")
|
.remove_props("componentMap")
|
||||||
)
|
)
|
||||||
|
tag.special_props.add(
|
||||||
|
Var.create_safe(
|
||||||
|
f"components={{{self._get_component_map_name()}()}}",
|
||||||
|
_var_is_local=True,
|
||||||
|
_var_is_string=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return tag
|
||||||
|
@ -8,6 +8,7 @@ from reflex.vars import Var, BaseVar, ComputedVar
|
|||||||
from reflex.event import EventChain, EventHandler, EventSpec
|
from reflex.event import EventChain, EventHandler, EventSpec
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
import textwrap
|
import textwrap
|
||||||
|
from hashlib import md5
|
||||||
from typing import Any, Callable, Dict, Union
|
from typing import Any, Callable, Dict, Union
|
||||||
from reflex.compiler import utils
|
from reflex.compiler import utils
|
||||||
from reflex.components.component import Component, CustomComponent
|
from reflex.components.component import Component, CustomComponent
|
||||||
|
Loading…
Reference in New Issue
Block a user